From ab54d5af8f91ca46abf3dd129f303f16323858ac Mon Sep 17 00:00:00 2001 From: Sabrina Demagny Date: Fri, 29 Mar 2024 17:26:51 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20allow=20to=20filter=20memb?= =?UTF-8?q?er=20on=20team=20access=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filter member by name... --- src/backend/core/api/viewsets.py | 17 ++ .../test_api_team_accesses_list.py | 148 ++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index fec5ff3..75f10be 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -347,6 +347,23 @@ class TeamAccessViewSet( queryset = queryset.filter(team=self.kwargs["team_id"]) if self.action in {"list", "retrieve"}: + if query := self.request.GET.get("q", ""): + similarity = Max( + TrigramSimilarity( + Coalesce(Func("user__email", function="unaccent"), Value("")), + Func(Value(query), function="unaccent"), + ) + + TrigramSimilarity( + Coalesce(Func("user__name", function="unaccent"), Value("")), + Func(Value(query), function="unaccent"), + ) + ) + queryset = ( + queryset.annotate(similarity=similarity) + .filter(similarity__gte=SIMILARITY_THRESHOLD) + .order_by("-similarity") + ) + # Determine which role the logged-in user has in the team user_role_query = models.TeamAccess.objects.filter( user=self.request.user, team=self.kwargs["team_id"] diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py index 47d5ff1..c841e76 100644 --- a/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py @@ -115,6 +115,154 @@ def test_api_team_accesses_list_authenticated_related(): ) +def test_api_team_accesses_list_find_members(django_assert_num_queries): + """ + Authenticated users should be able to search users access with a case-insensitive and + partial query on the name. + """ + dave = factories.UserFactory(name="dave bowman", email=None) + frank = factories.UserFactory(name="frank poole", email=None) + mary = factories.UserFactory(name="mary poole", email=None) + nicole = factories.UserFactory(name="nicole foole", email=None) + factories.UserFactory(email="tester@ministry.fr", name="john doe") + factories.UserFactory(name="heywood floyd", email=None) + factories.UserFactory(name="Andrei Smyslov", email=None) + factories.TeamFactory.create_batch(10) + + # Add Mary, Nicole and Dave in the same team + team = factories.TeamFactory( + name="Odyssey", + users=[ + (mary, models.RoleChoices.OWNER), + (nicole, models.RoleChoices.ADMIN), + (dave, models.RoleChoices.MEMBER), + ], + ) + factories.TeamFactory(users=[(frank, models.RoleChoices.MEMBER)]) + + # Search users in the team Odyssey + client = APIClient() + client.force_login(mary) + + # 5 queries are needed here: + # - 1 query: select on user authenticated + # - 4 queries: get all users, owner included (2 more queries) + with django_assert_num_queries(5): + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 3 + + # We can find David Bowman + # 3 queries are needed here: + # - 1 query: user authenticated + # - 2 queries: search user query with match + with django_assert_num_queries(3): + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/?q=bowman", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 1 + dave_access = dave.accesses.get(team=team) + assert response.json()["results"][0]["id"] == str(dave_access.id) + + # We can find Nicole + # 3 queries are needed here: + # - 1 query: user authenticated + # - 2 queries: search user query with match + with django_assert_num_queries(3): + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/?q=nicol", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 1 + nicole_access = nicole.accesses.get(team=team) + assert response.json()["results"][0]["id"] == str(nicole_access.id) + + # We can find Nicole and Mary + # 5 queries are needed here: + # - 1 query: select on user authenticated + # - 4 queries: search user query with match and the owner found (2 more queries) + with django_assert_num_queries(5): + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/?q=ool", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 2 + user_access_ids = sorted([user["id"] for user in response.json()["results"]]) + mary_access = mary.accesses.get(team=team) + assert sorted([str(mary_access.id), str(nicole_access.id)]) == user_access_ids + + # We cannot find Andrei, because he isn't a member of the current team + # 2 queries are needed here: + # - 1 query: select on user authenticated + # - 1 query: search user query with no match + with django_assert_num_queries(2): + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/?q=andrei", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 0 + + # We can find Mary + # 5 queries are needed here: + # - 1 query: select on user authenticated + # - 4 queries: search user query with match and an owner found (2 more queries) + with django_assert_num_queries(5): + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/?q=mary", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 1 + assert response.json()["results"][0]["id"] == str(mary_access.id) + + +def test_api_team_accesses__list_find_members_by_email(): + """ + Authenticated users should be able to search users access with a case-insensitive and + partial query on the email. + """ + user = factories.UserFactory(name=None) + + # set all names to None to match only on emails + colleague1 = factories.UserFactory(name=None, email="prudence_crandall@edu.us") + colleague2 = factories.UserFactory(name=None, email="reinebrunehaut@gouv.fr") + colleague3 = factories.UserFactory(name=None, email="artemisia.gentileschi@arte.it") + + # Add all colleague in the same team + team = factories.TeamFactory( + users=[ + (user, models.RoleChoices.ADMIN), + (colleague1, models.RoleChoices.OWNER), + (colleague2, models.RoleChoices.ADMIN), + (colleague3, models.RoleChoices.MEMBER), + ], + ) + factories.TeamAccessFactory.create_batch(4) + + # Create another team with similar email + factories.TeamFactory( + users=[ + ( + factories.UserFactory(name=None, email="bruneau@gouv.fr"), + models.RoleChoices.ADMIN, + ), + ], + ) + + # Search users in the team Odyssey + client = APIClient() + client.force_login(user) + + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/?q=BRUNE", + ) + assert response.status_code == HTTP_200_OK + assert response.json()["count"] == 1 + assert response.json()["results"][0]["user"]["email"] == "reinebrunehaut@gouv.fr" + + def test_api_team_accesses_list_authenticated_constant_numqueries( django_assert_num_queries, ):