From b5e75592e1e35d1042a9af6dfc90e4f1b5414794 Mon Sep 17 00:00:00 2001 From: wadii Date: Tue, 26 May 2026 12:57:56 +0200 Subject: [PATCH] feat: added type filter on get feature endpoint --- api/features/serializers.py | 6 +++ api/features/views.py | 3 ++ .../features/test_integration_features.py | 37 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/api/features/serializers.py b/api/features/serializers.py index 1e9463721629..1eca40fffe8e 100644 --- a/api/features/serializers.py +++ b/api/features/serializers.py @@ -53,6 +53,7 @@ from .feature_segments.serializers import ( CustomCreateSegmentOverrideFeatureSegmentSerializer, ) +from .feature_types import FEATURE_TYPE_CHOICES from .models import Feature, FeatureState from .multivariate.serializers import NestedMultivariateFeatureOptionSerializer @@ -95,6 +96,11 @@ class FeatureQuerySerializer(serializers.Serializer): # type: ignore[type-arg] ) is_archived = serializers.BooleanField(required=False) + type = serializers.ChoiceField( + choices=FEATURE_TYPE_CHOICES, + required=False, + help_text="Feature type to filter on (STANDARD or MULTIVARIATE).", + ) environment = serializers.IntegerField( required=False, help_text="Integer ID of the environment to view features in the context of.", diff --git a/api/features/views.py b/api/features/views.py index ad066819d3e7..74d7b81ae0a4 100644 --- a/api/features/views.py +++ b/api/features/views.py @@ -612,6 +612,9 @@ def _filter_queryset( if "is_archived" in query_serializer.initial_data: queryset = queryset.filter(is_archived=query_data["is_archived"]) + if query_data.get("type"): + queryset = queryset.filter(type=query_data["type"]) + queryset = self.filter_owners_and_group_owners(queryset, query_data) return queryset diff --git a/api/tests/integration/features/test_integration_features.py b/api/tests/integration/features/test_integration_features.py index c7cbca0c2089..8414636cb92f 100644 --- a/api/tests/integration/features/test_integration_features.py +++ b/api/tests/integration/features/test_integration_features.py @@ -1,5 +1,6 @@ import json +import pytest from django.urls import reverse from rest_framework import status @@ -203,3 +204,39 @@ def test_list_features__filter_by_archived_status__returns_matching_features( # list_features_is_archived_true_response.json()["results"][0]["id"] == archived_feature_id ) + + +@pytest.mark.parametrize( + "type_filter, expected_feature_name", + [ + ("STANDARD", "standard_feature"), + ("MULTIVARIATE", "multivariate_feature"), + ], +) +def test_list_features__filter_by_type__returns_matching_features( # type: ignore[no-untyped-def] + admin_client, project, type_filter, expected_feature_name +): + # Given + features_url = reverse("api-v1:projects:project-features-list", args=[project]) + admin_client.post( + features_url, + data={"name": "standard_feature", "project": project}, + ) + admin_client.post( + features_url, + data={ + "name": "multivariate_feature", + "project": project, + "type": "MULTIVARIATE", + "initial_value": "control", + }, + ) + + # When + response = admin_client.get(f"{features_url}?type={type_filter}") + + # Then + assert response.status_code == status.HTTP_200_OK + response_json = response.json() + assert response_json["count"] == 1 + assert response_json["results"][0]["name"] == expected_feature_name