|
3 | 3 | from parameterized import parameterized |
4 | 4 | from rest_framework import status |
5 | 5 |
|
| 6 | +from products.actions.backend.models.action import Action |
6 | 7 | from products.experiments.backend.experiment_saved_metric_service import ExperimentSavedMetricService |
7 | | -from products.experiments.backend.models.experiment import Experiment, ExperimentToSavedMetric |
| 8 | +from products.experiments.backend.models.experiment import Experiment, ExperimentSavedMetric, ExperimentToSavedMetric |
8 | 9 |
|
9 | 10 | from ee.api.test.base import APILicensedTest |
10 | 11 |
|
@@ -981,3 +982,73 @@ def test_list_paginates_with_limit_and_offset(self) -> None: |
981 | 982 | ids_page1 = {row["id"] for row in page1["results"]} |
982 | 983 | ids_page2 = {row["id"] for row in page2["results"]} |
983 | 984 | self.assertEqual(ids_page1 & ids_page2, set()) |
| 985 | + |
| 986 | + def _seed_metrics_for_event_filter(self) -> None: |
| 987 | + # Created via the ORM so the stored query JSON is exactly what the filter inspects. The action fires |
| 988 | + # on the "signup" event, so its metric is discoverable by that event — not by the action's label. |
| 989 | + signup_action = Action.objects.create(team=self.team, name="Completed signup", steps_json=[{"event": "signup"}]) |
| 990 | + for name, source in [ |
| 991 | + ("Pageview mean", {"kind": "EventsNode", "event": "$pageview"}), |
| 992 | + ("Purchase mean", {"kind": "EventsNode", "event": "purchase"}), |
| 993 | + ("Signup via action", {"kind": "ActionsNode", "id": signup_action.id, "name": "Completed signup"}), |
| 994 | + ]: |
| 995 | + ExperimentSavedMetric.objects.create( |
| 996 | + team=self.team, |
| 997 | + created_by=self.user, |
| 998 | + name=name, |
| 999 | + query={"kind": "ExperimentMetric", "metric_type": "mean", "source": source}, |
| 1000 | + ) |
| 1001 | + |
| 1002 | + @parameterized.expand( |
| 1003 | + [ |
| 1004 | + # `event` matches the events a metric references — directly, or via an action's step events. |
| 1005 | + ("direct_event", "$pageview", {"Pageview mean"}), |
| 1006 | + ("another_direct_event", "purchase", {"Purchase mean"}), |
| 1007 | + ("event_behind_an_action", "signup", {"Signup via action"}), |
| 1008 | + # It matches events, not the action's label, nor query structure/type tokens. |
| 1009 | + ("action_label_not_matched", "Completed signup", set()), |
| 1010 | + ("metric_type_token_not_matched", "mean", set()), |
| 1011 | + ("node_kind_token_not_matched", "EventsNode", set()), |
| 1012 | + ("no_match", "not_an_event", set()), |
| 1013 | + ] |
| 1014 | + ) |
| 1015 | + def test_event_filter_matches_referenced_events_only( |
| 1016 | + self, _name: str, event: str, expected_names: set[str] |
| 1017 | + ) -> None: |
| 1018 | + self._seed_metrics_for_event_filter() |
| 1019 | + |
| 1020 | + response = self.client.get(f"/api/projects/{self.team.id}/experiment_saved_metrics/", data={"event": event}) |
| 1021 | + |
| 1022 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 1023 | + returned = {row["name"] for row in response.json()["results"]} |
| 1024 | + self.assertEqual(returned, expected_names) |
| 1025 | + |
| 1026 | + def test_event_filter_composes_with_search(self) -> None: |
| 1027 | + # `event` (references) and `search` (name/description/tags) apply through different mechanisms; |
| 1028 | + # both must narrow the result set together (AND), not clobber each other. |
| 1029 | + purchase_query = { |
| 1030 | + "kind": "ExperimentMetric", |
| 1031 | + "metric_type": "mean", |
| 1032 | + "source": {"kind": "EventsNode", "event": "purchase"}, |
| 1033 | + } |
| 1034 | + for name in ["Alpha purchase", "Beta purchase"]: |
| 1035 | + ExperimentSavedMetric.objects.create(team=self.team, created_by=self.user, name=name, query=purchase_query) |
| 1036 | + |
| 1037 | + response = self.client.get( |
| 1038 | + f"/api/projects/{self.team.id}/experiment_saved_metrics/?event=purchase&search=Alpha" |
| 1039 | + ) |
| 1040 | + |
| 1041 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 1042 | + returned = {row["name"] for row in response.json()["results"]} |
| 1043 | + self.assertEqual(returned, {"Alpha purchase"}) |
| 1044 | + |
| 1045 | + def test_event_param_does_not_filter_detail_retrieve(self) -> None: |
| 1046 | + # `event` is a list-only filter; it must never narrow a detail lookup into a 404. |
| 1047 | + metric_id = self._create_saved_metric("Pageview mean") |
| 1048 | + |
| 1049 | + response = self.client.get( |
| 1050 | + f"/api/projects/{self.team.id}/experiment_saved_metrics/{metric_id}?event=not_an_event" |
| 1051 | + ) |
| 1052 | + |
| 1053 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 1054 | + self.assertEqual(response.json()["id"], metric_id) |
0 commit comments