Skip to content

Commit

Permalink
Handle versioned environments for associated-features endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewelwell committed Oct 16, 2024
1 parent 324fe12 commit 632884f
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 4 deletions.
4 changes: 4 additions & 0 deletions api/features/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,10 @@ class Meta:
fields = ("id", "feature", "environment")


class AssociatedFeaturesQuerySerializer(serializers.Serializer):
environment = serializers.IntegerField(required=False)


class SDKFeatureStatesQuerySerializer(serializers.Serializer):
feature = serializers.CharField(
required=False, help_text="Name of the feature to get the state of"
Expand Down
26 changes: 24 additions & 2 deletions api/segments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
from app.pagination import CustomPagination
from edge_api.identities.models import EdgeIdentity
from environments.identities.models import Identity
from environments.models import Environment
from features.models import FeatureState
from features.serializers import SegmentAssociatedFeatureStateSerializer
from features.serializers import (
AssociatedFeaturesQuerySerializer,
SegmentAssociatedFeatureStateSerializer,
)
from features.versioning.models import EnvironmentFeatureVersion
from projects.permissions import VIEW_PROJECT

from .models import Segment
Expand Down Expand Up @@ -77,6 +82,7 @@ def get_queryset(self):

return queryset

@swagger_auto_schema(query_serializer=AssociatedFeaturesQuerySerializer())
@action(
detail=True,
methods=["GET"],
Expand All @@ -85,7 +91,23 @@ def get_queryset(self):
)
def associated_features(self, request, *args, **kwargs):
segment = self.get_object()
queryset = FeatureState.objects.filter(feature_segment__segment=segment)

query_serializer = AssociatedFeaturesQuerySerializer(data=request.query_params)
query_serializer.is_valid(raise_exception=True)

query_kwargs = {"feature_segment__segment": segment}

if environment_id := query_serializer.validated_data.get("environment"):
environment = Environment.objects.get(pk=environment_id)
query_kwargs["environment"] = environment
if environment.use_v2_feature_versioning and (
latest_version_uuids := EnvironmentFeatureVersion.objects.get_latest_versions_by_environment_id(
environment_id
)
):
query_kwargs["environment_feature_version__in"] = latest_version_uuids

queryset = FeatureState.objects.filter(**query_kwargs)

page = self.paginate_queryset(queryset)
if page is not None:
Expand Down
72 changes: 71 additions & 1 deletion api/tests/unit/segments/test_unit_segments_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from audit.models import AuditLog
from audit.related_object_type import RelatedObjectType
from environments.models import Environment
from features.models import Feature
from features.models import Feature, FeatureSegment, FeatureState
from features.versioning.models import EnvironmentFeatureVersion
from metadata.models import Metadata, MetadataModelField
from projects.models import Project
from projects.permissions import MANAGE_SEGMENTS, VIEW_PROJECT
Expand Down Expand Up @@ -323,6 +324,75 @@ def test_associated_features_returns_all_the_associated_features(
assert response.json()["results"][0]["environment"] == environment.id


@pytest.mark.parametrize(
"client",
[lazy_fixture("admin_master_api_key_client"), lazy_fixture("admin_client")],
)
def test_associated_features_returns_only_latest_versions_of_associated_features(
project: Project,
segment: Segment,
environment_v2_versioning: Environment,
client: APIClient,
) -> None:
# Given
# 2 features
feature_one = Feature.objects.create(project=project, name="feature_1")
feature_two = Feature.objects.create(project=project, name="feature_2")

# Now let's create a version for each feature with a segment override
for feature in (feature_one, feature_two):
version = EnvironmentFeatureVersion.objects.create(
feature=feature, environment=environment_v2_versioning
)
FeatureState.objects.create(
feature=feature,
environment=environment_v2_versioning,
environment_feature_version=version,
feature_segment=FeatureSegment.objects.create(
segment=segment,
environment=environment_v2_versioning,
feature=feature,
environment_feature_version=version,
),
)
version.publish()

# And then let's create a third version for feature_one where we update the segment override
feature_1_version_3 = EnvironmentFeatureVersion.objects.create(
feature=feature_one, environment=environment_v2_versioning
)
f1v3_segment_override_feature_state = feature_1_version_3.feature_states.get(
feature_segment__segment=segment
)
f1v3_segment_override_feature_state.enabled = True
f1v3_segment_override_feature_state.save()
feature_1_version_3.publish()

# And finally, let's create a third version for feature_two where we remove the segment override
feature_2_version_3 = EnvironmentFeatureVersion.objects.create(
feature=feature_two, environment=environment_v2_versioning
)
feature_2_version_3.feature_states.filter(feature_segment__segment=segment).delete()
feature_2_version_3.publish()

url = "%s?environment=%s" % (
reverse(
"api-v1:projects:project-segments-associated-features",
args=[project.id, segment.id],
),
environment_v2_versioning.id,
)

# When
response = client.get(url)

# Then
assert response.json().get("count") == 1
assert response.json()["results"][0]["id"] == f1v3_segment_override_feature_state.id
assert response.json()["results"][0]["feature"] == feature_one.id
assert response.json()["results"][0]["environment"] == environment_v2_versioning.id


@pytest.mark.parametrize(
"client",
[lazy_fixture("admin_master_api_key_client"), lazy_fixture("admin_client")],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class TheComponent extends Component {
fetch = () => {
_data
.get(
`${Project.api}projects/${this.props.projectId}/segments/${this.props.id}/associated-features/`,
`${Project.api}projects/${this.props.projectId}/segments/${this.props.id}/associated-features/?environment=${this.props.environmentId}`,
)
.then((v) =>
Promise.all(
Expand Down
1 change: 1 addition & 0 deletions frontend/web/components/modals/CreateSegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
projectId={projectId}
id={segment.id}
readOnly={isReadOnly}
environmentId={environmentId}
/>
)
}}
Expand Down

0 comments on commit 632884f

Please sign in to comment.