diff --git a/api/experimentation/views.py b/api/experimentation/views.py index c26dfa639a6c..ceb62434f3ff 100644 --- a/api/experimentation/views.py +++ b/api/experimentation/views.py @@ -195,13 +195,14 @@ def get_queryset(self) -> "QuerySet[Experiment]": "feature__feature_states__multivariate_feature_state_values", "experiment_metrics__metric", ) - status_filter = self.request.query_params.get("status") + status_filter = self.request.query_params.getlist("status") if status_filter: - if status_filter not in ExperimentStatus.values: + invalid = [s for s in status_filter if s not in ExperimentStatus.values] + if invalid: raise serializers.ValidationError( - {"status": f"Invalid status '{status_filter}'."} + {"status": f"Invalid status value(s): {', '.join(invalid)}."} ) - qs = qs.filter(status=status_filter) + qs = qs.filter(status__in=status_filter) q = self.request.query_params.get("q") if q: diff --git a/api/tests/unit/experimentation/test_experiment_views.py b/api/tests/unit/experimentation/test_experiment_views.py index 933e106e7a87..2b7ec0fe1b5d 100644 --- a/api/tests/unit/experimentation/test_experiment_views.py +++ b/api/tests/unit/experimentation/test_experiment_views.py @@ -1156,6 +1156,48 @@ def test_delete__valid_delete__creates_audit_log( assert "deleted" in audit.log +def test_get_list__filter_by_multiple_statuses__returns_matching( + admin_client_new: APIClient, + environment: Environment, + experiment: Experiment, + project: "Project", + enable_features: EnableFeaturesFixture, +) -> None: + # Given + enable_features(EXPERIMENT_FLAG) + second_feature = Feature.objects.create( + name="mv_feature_2", + project=project, + type=MULTIVARIATE, + initial_value="control", + ) + for pct in (50, 50): + MultivariateFeatureOption.objects.create( + feature=second_feature, + default_percentage_allocation=pct, + type="unicode", + string_value=f"option_{pct}", + ) + running_experiment = Experiment.objects.create( + environment=environment, + feature=second_feature, + name="Running Experiment", + hypothesis="hypothesis", + status=ExperimentStatus.RUNNING, + ) + + # When — filter for both created and running + response = admin_client_new.get( + _list_url(environment), + {"status": ["created", "running"]}, + ) + + # Then + assert response.status_code == status.HTTP_200_OK + result_ids = {r["id"] for r in response.json()["results"]} + assert result_ids == {experiment.id, running_experiment.id} + + def test_get_list__invalid_status__returns_400( admin_client_new: APIClient, environment: Environment, @@ -1171,6 +1213,24 @@ def test_get_list__invalid_status__returns_400( assert response.status_code == status.HTTP_400_BAD_REQUEST +def test_get_list__mixed_valid_and_invalid_status__returns_400( + admin_client_new: APIClient, + environment: Environment, + enable_features: EnableFeaturesFixture, +) -> None: + # Given + enable_features(EXPERIMENT_FLAG) + + # When + response = admin_client_new.get( + _list_url(environment), + {"status": ["running", "garbage"]}, + ) + + # Then + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_delete__running_experiment__returns_400( admin_client_new: APIClient, environment: Environment,