Skip to content

Commit bed542b

Browse files
authored
Merge branch 'main' into feat/environments-v2-opt-in
2 parents 5ef5c06 + e8d1337 commit bed542b

File tree

72 files changed

+2230
-1401
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2230
-1401
lines changed

.release-please-manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.114.0"
2+
".": "2.115.0"
33
}

CHANGELOG.md

+30
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
# Changelog
22

3+
## [2.115.0](https://github.com/Flagsmith/flagsmith/compare/v2.114.1...v2.115.0) (2024-05-15)
4+
5+
6+
### Features
7+
8+
* Add metadata fields to core entities (API) ([#3315](https://github.com/Flagsmith/flagsmith/issues/3315)) ([06eb8a4](https://github.com/Flagsmith/flagsmith/commit/06eb8a4754bf8afbff29974eb6e075953ca0352d))
9+
10+
11+
### Bug Fixes
12+
13+
* add trailing slash to update group logic ([#3943](https://github.com/Flagsmith/flagsmith/issues/3943)) ([95b14d1](https://github.com/Flagsmith/flagsmith/commit/95b14d121d0a8d2419746ac5112b6ca78670f5d9))
14+
* changed the error message from custom_auth serializer ([#3924](https://github.com/Flagsmith/flagsmith/issues/3924)) ([185bd6a](https://github.com/Flagsmith/flagsmith/commit/185bd6a441af53fefec2426a891df0a1d140af58))
15+
* Create GitHub comment as table ([#3948](https://github.com/Flagsmith/flagsmith/issues/3948)) ([bf67b1d](https://github.com/Flagsmith/flagsmith/commit/bf67b1dc0ad3a34c38f8aabb8a5cfaf5f65863de))
16+
* Organisation ID is an object calling useHasPermission at organisation level ([#3950](https://github.com/Flagsmith/flagsmith/issues/3950)) ([1372917](https://github.com/Flagsmith/flagsmith/commit/13729173d0587c322272f603f1957908874f9375))
17+
* organisation id parsing ([#3954](https://github.com/Flagsmith/flagsmith/issues/3954)) ([aae116b](https://github.com/Flagsmith/flagsmith/commit/aae116bc6fd83837e6ead8bc681155c25149dcdc))
18+
* Scroll to top on path change ([#3926](https://github.com/Flagsmith/flagsmith/issues/3926)) ([1a2e793](https://github.com/Flagsmith/flagsmith/commit/1a2e793e9bb47922ad0c688c445a54d5ff2677db))
19+
* segment override link ([#3945](https://github.com/Flagsmith/flagsmith/issues/3945)) ([fc0cceb](https://github.com/Flagsmith/flagsmith/commit/fc0cceb40b881891878678c1c61cc0cc1148d090))
20+
* Validate and handle URL params ([#3932](https://github.com/Flagsmith/flagsmith/issues/3932)) ([7e1617f](https://github.com/Flagsmith/flagsmith/commit/7e1617f5bd4e754dbc7d17db957f4315c53c3fba))
21+
* **versioning:** prevent task from deleting all unrelated feature states / feature segments ([#3955](https://github.com/Flagsmith/flagsmith/issues/3955)) ([0ed5148](https://github.com/Flagsmith/flagsmith/commit/0ed5148183e21a74ad93aa9e0af47a08941cfed6))
22+
23+
## [2.114.1](https://github.com/Flagsmith/flagsmith/compare/v2.114.0...v2.114.1) (2024-05-14)
24+
25+
26+
### Bug Fixes
27+
28+
* Add multivariate values when cloning identities ([#3894](https://github.com/Flagsmith/flagsmith/issues/3894)) ([92e3e9f](https://github.com/Flagsmith/flagsmith/commit/92e3e9f55c25855cd0d1b7b7333184ab54385846))
29+
* Organisation id not numeric in organisation settings ([#3929](https://github.com/Flagsmith/flagsmith/issues/3929)) ([9e3746b](https://github.com/Flagsmith/flagsmith/commit/9e3746bd7dce9a8d2eac6bf616a73f2dd1f5b54e))
30+
* **versioning:** fix exception getting feature states for edge identity post v2 versioning migration ([#3916](https://github.com/Flagsmith/flagsmith/issues/3916)) ([132ef77](https://github.com/Flagsmith/flagsmith/commit/132ef77ee0d50c618c23431e3ea1b60aec3e5bf4))
31+
* **versioning:** handle mapping of environment to engine post v2 versioning migration ([#3913](https://github.com/Flagsmith/flagsmith/issues/3913)) ([75acd12](https://github.com/Flagsmith/flagsmith/commit/75acd12c632a9fe8b4a5af5a48a33d18a406e9d4))
32+
333
## [2.114.0](https://github.com/Flagsmith/flagsmith/compare/v2.113.0...v2.114.0) (2024-05-10)
434

535

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ REST calls to the API.
7575
- [Website](https://flagsmith.com/)
7676
- [Documentation](https://docs.flagsmith.com/)
7777
- If you have any questions about our projects you can email [[email protected]](mailto:[email protected])
78+
79+
## Acknowledgements
80+
81+
Thank you to [Uffizzi](https://www.uffizzi.com) for providing ephemeral environments to preview pull requests.

api/app/settings/common.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,18 @@
11981198

11991199
ENABLE_API_USAGE_ALERTING = env.bool("ENABLE_API_USAGE_ALERTING", default=False)
12001200

1201+
# See DomainAuthMethods in flagsmith-auth-controller repository with auth_controller.models module
1202+
GLOBAL_DOMAIN_AUTH_METHODS = env.dict(
1203+
"GLOBAL_DOMAIN_AUTH_METHODS",
1204+
{
1205+
"EP": True, # Email / Password
1206+
"GO": True, # Google
1207+
"GH": True, # GitHub
1208+
"SAML": True, # Security Assertion Markup Language
1209+
},
1210+
)
1211+
12011212
EDGE_V2_MIGRATION_READ_CAPACITY_BUDGET = env.int(
12021213
"EDGE_V2_MIGRATION_READ_CAPACITY_BUDGET",
12031214
default=0,
1204-
)
1215+
)

api/conftest.py

+173-16
Original file line numberDiff line numberDiff line change
@@ -336,15 +336,16 @@ def with_environment_permissions(
336336
"""
337337

338338
def _with_environment_permissions(
339-
permission_keys: list[str],
339+
permission_keys: list[str] | None = None,
340340
environment_id: int | None = None,
341341
admin: bool = False,
342342
) -> UserEnvironmentPermission:
343343
environment_id = environment_id or environment.id
344344
uep, __ = UserEnvironmentPermission.objects.get_or_create(
345345
environment_id=environment_id, user=staff_user, defaults={"admin": admin}
346346
)
347-
uep.permissions.add(*permission_keys)
347+
if permission_keys:
348+
uep.permissions.add(*permission_keys)
348349

349350
return uep
350351

@@ -384,7 +385,7 @@ def with_project_permissions(
384385
"""
385386

386387
def _with_project_permissions(
387-
permission_keys: list[str] = None,
388+
permission_keys: list[str] | None = None,
388389
project_id: typing.Optional[int] = None,
389390
admin: bool = False,
390391
) -> UserProjectPermission:
@@ -552,6 +553,29 @@ def segment_featurestate(feature_segment, feature, environment):
552553
)
553554

554555

556+
@pytest.fixture()
557+
def feature_with_value_segment(
558+
feature_with_value: Feature, segment: Segment, environment: Environment
559+
) -> FeatureSegment:
560+
return FeatureSegment.objects.create(
561+
feature=feature_with_value, segment=segment, environment=environment
562+
)
563+
564+
565+
@pytest.fixture()
566+
def segment_featurestate_and_feature_with_value(
567+
feature_with_value_segment: FeatureSegment,
568+
feature_with_value: Feature,
569+
environment: Environment,
570+
) -> FeatureState:
571+
return FeatureState.objects.create(
572+
feature_segment=feature_with_value_segment,
573+
feature=feature_with_value,
574+
environment=environment,
575+
updated_at="2024-01-01 00:00:00",
576+
)
577+
578+
555579
@pytest.fixture()
556580
def environment_api_key(environment):
557581
return EnvironmentAPIKey.objects.create(
@@ -658,23 +682,23 @@ def task_processor_synchronously(settings):
658682

659683

660684
@pytest.fixture()
661-
def a_metadata_field(organisation):
685+
def a_metadata_field(organisation: Organisation) -> MetadataField:
662686
return MetadataField.objects.create(name="a", type="int", organisation=organisation)
663687

664688

665689
@pytest.fixture()
666-
def b_metadata_field(organisation):
690+
def b_metadata_field(organisation: Organisation) -> MetadataField:
667691
return MetadataField.objects.create(name="b", type="str", organisation=organisation)
668692

669693

670694
@pytest.fixture()
671695
def required_a_environment_metadata_field(
672-
organisation,
673-
a_metadata_field,
674-
environment,
675-
project,
676-
project_content_type,
677-
):
696+
organisation: Organisation,
697+
a_metadata_field: MetadataField,
698+
environment: Environment,
699+
project: Project,
700+
project_content_type: ContentType,
701+
) -> MetadataModelField:
678702
environment_type = ContentType.objects.get_for_model(environment)
679703
model_field = MetadataModelField.objects.create(
680704
field=a_metadata_field,
@@ -688,7 +712,119 @@ def required_a_environment_metadata_field(
688712

689713

690714
@pytest.fixture()
691-
def optional_b_environment_metadata_field(organisation, b_metadata_field, environment):
715+
def required_a_feature_metadata_field(
716+
organisation: Organisation,
717+
a_metadata_field: MetadataField,
718+
feature_content_type: ContentType,
719+
project: Project,
720+
project_content_type: ContentType,
721+
) -> MetadataModelField:
722+
model_field = MetadataModelField.objects.create(
723+
field=a_metadata_field,
724+
content_type=feature_content_type,
725+
)
726+
727+
MetadataModelFieldRequirement.objects.create(
728+
content_type=project_content_type, object_id=project.id, model_field=model_field
729+
)
730+
731+
return model_field
732+
733+
734+
@pytest.fixture()
735+
def required_a_feature_metadata_field_using_organisation_content_type(
736+
organisation: Organisation,
737+
a_metadata_field: MetadataField,
738+
feature_content_type: ContentType,
739+
project: Project,
740+
organisation_content_type: ContentType,
741+
) -> MetadataModelField:
742+
model_field = MetadataModelField.objects.create(
743+
field=a_metadata_field,
744+
content_type=feature_content_type,
745+
)
746+
747+
MetadataModelFieldRequirement.objects.create(
748+
content_type=organisation_content_type,
749+
object_id=organisation.id,
750+
model_field=model_field,
751+
)
752+
753+
return model_field
754+
755+
756+
@pytest.fixture()
757+
def required_a_segment_metadata_field(
758+
organisation: Organisation,
759+
a_metadata_field: MetadataField,
760+
segment_content_type: ContentType,
761+
project: Project,
762+
project_content_type: ContentType,
763+
) -> MetadataModelField:
764+
model_field = MetadataModelField.objects.create(
765+
field=a_metadata_field,
766+
content_type=segment_content_type,
767+
)
768+
769+
MetadataModelFieldRequirement.objects.create(
770+
content_type=project_content_type, object_id=project.id, model_field=model_field
771+
)
772+
773+
return model_field
774+
775+
776+
@pytest.fixture()
777+
def required_a_segment_metadata_field_using_organisation_content_type(
778+
organisation: Organisation,
779+
a_metadata_field: MetadataField,
780+
segment_content_type: ContentType,
781+
project: Project,
782+
organisation_content_type: ContentType,
783+
) -> MetadataModelField:
784+
model_field = MetadataModelField.objects.create(
785+
field=a_metadata_field,
786+
content_type=segment_content_type,
787+
)
788+
789+
MetadataModelFieldRequirement.objects.create(
790+
content_type=organisation_content_type,
791+
object_id=organisation.id,
792+
model_field=model_field,
793+
)
794+
795+
return model_field
796+
797+
798+
@pytest.fixture()
799+
def optional_b_feature_metadata_field(
800+
organisation: Organisation, b_metadata_field: MetadataField, feature: Feature
801+
) -> MetadataModelField:
802+
feature_type = ContentType.objects.get_for_model(feature)
803+
804+
return MetadataModelField.objects.create(
805+
field=b_metadata_field,
806+
content_type=feature_type,
807+
)
808+
809+
810+
@pytest.fixture()
811+
def optional_b_segment_metadata_field(
812+
organisation: Organisation, b_metadata_field: MetadataField, segment: Segment
813+
) -> MetadataModelField:
814+
segment_type = ContentType.objects.get_for_model(segment)
815+
816+
return MetadataModelField.objects.create(
817+
field=b_metadata_field,
818+
content_type=segment_type,
819+
)
820+
821+
822+
@pytest.fixture()
823+
def optional_b_environment_metadata_field(
824+
organisation: Organisation,
825+
b_metadata_field: MetadataField,
826+
environment: Environment,
827+
) -> MetadataModelField:
692828
environment_type = ContentType.objects.get_for_model(environment)
693829

694830
return MetadataModelField.objects.create(
@@ -698,7 +834,10 @@ def optional_b_environment_metadata_field(organisation, b_metadata_field, enviro
698834

699835

700836
@pytest.fixture()
701-
def environment_metadata_a(environment, required_a_environment_metadata_field):
837+
def environment_metadata_a(
838+
environment: Environment,
839+
required_a_environment_metadata_field: MetadataModelField,
840+
) -> Metadata:
702841
environment_type = ContentType.objects.get_for_model(environment)
703842
return Metadata.objects.create(
704843
object_id=environment.id,
@@ -709,7 +848,10 @@ def environment_metadata_a(environment, required_a_environment_metadata_field):
709848

710849

711850
@pytest.fixture()
712-
def environment_metadata_b(environment, optional_b_environment_metadata_field):
851+
def environment_metadata_b(
852+
environment: Environment,
853+
optional_b_environment_metadata_field: MetadataModelField,
854+
) -> Metadata:
713855
environment_type = ContentType.objects.get_for_model(environment)
714856
return Metadata.objects.create(
715857
object_id=environment.id,
@@ -720,15 +862,30 @@ def environment_metadata_b(environment, optional_b_environment_metadata_field):
720862

721863

722864
@pytest.fixture()
723-
def environment_content_type():
865+
def environment_content_type() -> ContentType:
724866
return ContentType.objects.get_for_model(Environment)
725867

726868

727869
@pytest.fixture()
728-
def project_content_type():
870+
def feature_content_type() -> ContentType:
871+
return ContentType.objects.get_for_model(Feature)
872+
873+
874+
@pytest.fixture()
875+
def segment_content_type() -> ContentType:
876+
return ContentType.objects.get_for_model(Segment)
877+
878+
879+
@pytest.fixture()
880+
def project_content_type() -> ContentType:
729881
return ContentType.objects.get_for_model(Project)
730882

731883

884+
@pytest.fixture()
885+
def organisation_content_type() -> ContentType:
886+
return ContentType.objects.get_for_model(Organisation)
887+
888+
732889
@pytest.fixture
733890
def manage_user_group_permission(db):
734891
return OrganisationPermissionModel.objects.get(key=MANAGE_USER_GROUPS)

api/custom_auth/serializers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Meta(UserCreateSerializer.Meta):
4040
UniqueValidator(
4141
queryset=FFAdminUser.objects.all(),
4242
lookup="iexact",
43-
message="Invalid email address.",
43+
message="Email already exists. Please log in.",
4444
)
4545
]
4646
}

api/edge_api/identities/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,6 @@ def clone_flag_states_from(self, source_identity: "EdgeIdentity") -> None:
259259
feature=feature_in_source.feature,
260260
feature_state_value=feature_in_source.feature_state_value,
261261
enabled=feature_in_source.enabled,
262+
multivariate_feature_state_values=feature_in_source.multivariate_feature_state_values,
262263
)
263264
self.add_feature_override(feature_state_target)

api/environments/constants.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
IDENTITY_INTEGRATIONS_RELATION_NAMES = [
2+
"amplitude_config",
3+
"heap_config",
4+
"mixpanel_config",
5+
"rudderstack_config",
6+
"segment_config",
7+
"webhook_config",
8+
]

0 commit comments

Comments
 (0)