From 4f9c088f6b0458eb0ebb52ef899cdfdc57f2c43c Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 00:59:41 +0200 Subject: Add route to get a member's data for helper review Added route for getting a user's join date, total messages, and top 3 channels by activity. This information will be used to auto-review nominees. --- pydis_site/apps/api/models/bot/metricity.py | 39 +++++++++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/user.py | 15 +++++++++++ 2 files changed, 54 insertions(+) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index cae630f1..af5e1f3b 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -89,3 +89,42 @@ class Metricity: raise NotFound() return values[0] + + def top_channel_activity(self, user_id: str) -> int: + """ + Query the top three channels in which the user is most active. + + Help channels are grouped under "the help channels", + and off-topic channels are grouped under "off-topic". + """ + self.cursor.execute( + """ + SELECT + CASE + WHEN channels.name ILIKE 'help-%%' THEN 'the help channels' + WHEN channels.name ILIKE 'ot%%' THEN 'off-topic' + ELSE channels.name + END, + COUNT(1) + FROM + messages + LEFT JOIN channels ON channels.id = messages.channel_id + WHERE + author_id = '%s' + GROUP BY + 1 + ORDER BY + 2 DESC + LIMIT + 3; + """, + [user_id] + ) + + values = self.cursor.fetchall() + print(values) + + if not values: + raise NotFound() + + return values diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 829e2694..5e1f8775 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -262,3 +262,18 @@ class UserViewSet(ModelViewSet): except NotFound: return Response(dict(detail="User not found in metricity"), status=status.HTTP_404_NOT_FOUND) + + @action(detail=True) + def metricity_review_data(self, request: Request, pk: str = None) -> Response: + """Request handler for metricity_review_data endpoint.""" + user = self.get_object() + + with Metricity() as metricity: + try: + data = metricity.user(user.id) + data["total_messages"] = metricity.total_messages(user.id) + data["top_channel_activity"] = metricity.top_channel_activity(user.id) + return Response(data, status=status.HTTP_200_OK) + except NotFound: + return Response(dict(detail="User not found in metricity"), + status=status.HTTP_404_NOT_FOUND) -- cgit v1.2.3 From d2690bbedb5f5ef221cbfaa42ad78ff8fcc263f2 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 01:05:09 +0200 Subject: Amend top_channel_activity return type --- pydis_site/apps/api/models/bot/metricity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index af5e1f3b..29a43513 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -1,3 +1,5 @@ +from typing import List, Tuple + from django.db import connections BLOCK_INTERVAL = 10 * 60 # 10 minute blocks @@ -90,7 +92,7 @@ class Metricity: return values[0] - def top_channel_activity(self, user_id: str) -> int: + def top_channel_activity(self, user_id: str) -> List[Tuple[str, int]]: """ Query the top three channels in which the user is most active. -- cgit v1.2.3 From 3468d12b0c893bfa951e7baa85ada6a8716e5aef Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 01:39:12 +0200 Subject: Added test for metricity-review-data --- pydis_site/apps/api/tests/test_users.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 69bbfefc..5851ac11 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -410,7 +410,7 @@ class UserMetricityTests(APISubdomainTestCase): joined_at = "foo" total_messages = 1 total_blocks = 1 - self.mock_metricity_user(joined_at, total_messages, total_blocks) + self.mock_metricity_user(joined_at, total_messages, total_blocks, []) # When url = reverse('bot:user-metricity-data', args=[0], host='api') @@ -442,7 +442,7 @@ class UserMetricityTests(APISubdomainTestCase): {'exception': ObjectDoesNotExist, 'voice_banned': False}, ] - self.mock_metricity_user("foo", 1, 1) + self.mock_metricity_user("foo", 1, 1, [["bar", 1]]) for case in cases: with self.subTest(exception=case['exception'], voice_banned=case['voice_banned']): @@ -455,7 +455,27 @@ class UserMetricityTests(APISubdomainTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["voice_banned"], case["voice_banned"]) - def mock_metricity_user(self, joined_at, total_messages, total_blocks): + def test_metricity_review_data(self): + # Given + joined_at = "foo" + total_messages = 10 + total_blocks = 1 + channel_activity = [["bar", 4], ["buzz", 6]] + self.mock_metricity_user(joined_at, total_messages, total_blocks, channel_activity) + + # When + url = reverse('bot:user-metricity-review-data', args=[0], host='api') + response = self.client.get(url) + + # Then + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), { + "joined_at": joined_at, + "top_channel_activity": channel_activity, + "total_messages": total_messages + }) + + def mock_metricity_user(self, joined_at, total_messages, total_blocks, top_channel_activity): patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") self.metricity = patcher.start() self.addCleanup(patcher.stop) @@ -463,6 +483,7 @@ class UserMetricityTests(APISubdomainTestCase): self.metricity.user.return_value = dict(joined_at=joined_at) self.metricity.total_messages.return_value = total_messages self.metricity.total_message_blocks.return_value = total_blocks + self.metricity.top_channel_activity.return_value = top_channel_activity def mock_no_metricity_user(self): patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") @@ -472,3 +493,4 @@ class UserMetricityTests(APISubdomainTestCase): self.metricity.user.side_effect = NotFound() self.metricity.total_messages.side_effect = NotFound() self.metricity.total_message_blocks.side_effect = NotFound() + self.metricity.top_channel_activity.side_effect = NotFound() -- cgit v1.2.3 From 66047f06e5ce060e79e49ef2dfab06475298b070 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 01:46:43 +0200 Subject: Test metricity-review-data when user doesn't exist --- pydis_site/apps/api/tests/test_users.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 5851ac11..2ac06f6b 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -431,10 +431,13 @@ class UserMetricityTests(APISubdomainTestCase): # When url = reverse('bot:user-metricity-data', args=[0], host='api') - response = self.client.get(url) + response1 = self.client.get(url) + url = reverse('bot:user-metricity-review-data', args=[0], host='api') + response2 = self.client.get(url) # Then - self.assertEqual(response.status_code, 404) + self.assertEqual(response1.status_code, 404) + self.assertEqual(response2.status_code, 404) def test_metricity_voice_banned(self): cases = [ -- cgit v1.2.3 From 07847e959c9b2ac6a79ab38b900abc2d179d0478 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 11:58:30 +0200 Subject: Get rid of stray print Oops. --- pydis_site/apps/api/models/bot/metricity.py | 1 - 1 file changed, 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 29a43513..7e2a68f2 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -124,7 +124,6 @@ class Metricity: ) values = self.cursor.fetchall() - print(values) if not values: raise NotFound() -- cgit v1.2.3 From fdc1be68a90d6ebd9dfe29369bc8a974bcaa8214 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Thu, 11 Mar 2021 02:37:44 +0200 Subject: Ignore deleted messaages in message counts Co-authored-by: Joe Banks --- pydis_site/apps/api/models/bot/metricity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 7e2a68f2..db975d4e 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -112,7 +112,7 @@ class Metricity: messages LEFT JOIN channels ON channels.id = messages.channel_id WHERE - author_id = '%s' + author_id = '%s' AND NOT messages.is_deleted GROUP BY 1 ORDER BY -- cgit v1.2.3 From f4a67489b81f95978912582189cb23afb2169e8e Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Fri, 12 Mar 2021 15:35:47 +0200 Subject: Document endpoint in viewset docstring --- pydis_site/apps/api/viewsets/bot/user.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 5e1f8775..25722f5a 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -119,6 +119,22 @@ class UserViewSet(ModelViewSet): - 200: returned on success - 404: if a user with the given `snowflake` could not be found + ### GET /bot/users//metricity_review_data + Gets metricity data for a single user's review by ID. + + #### Response format + >>> { + ... 'joined_at': '2020-08-26T08:09:43.507000', + ... 'top_channel_activity': [['off-topic', 15], + ... ['talent-pool', 4], + ... ['defcon', 2]], + ... 'total_messages': 22 + ... } + + #### Status codes + - 200: returned on success + - 404: if a user with the given `snowflake` could not be found + ### POST /bot/users Adds a single or multiple new users. The roles attached to the user(s) must be roles known by the site. -- cgit v1.2.3 From 9b1b42a4899c0c3f47845def5d152e2bed6c8dd0 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Fri, 12 Mar 2021 15:59:11 +0200 Subject: Split test_no_metricity_user to two tests by endpoints --- pydis_site/apps/api/tests/test_users.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 2ac06f6b..c43b916a 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -431,13 +431,21 @@ class UserMetricityTests(APISubdomainTestCase): # When url = reverse('bot:user-metricity-data', args=[0], host='api') - response1 = self.client.get(url) + response = self.client.get(url) + + # Then + self.assertEqual(response.status_code, 404) + + def test_no_metricity_user_for_review(self): + # Given + self.mock_no_metricity_user() + + # When url = reverse('bot:user-metricity-review-data', args=[0], host='api') - response2 = self.client.get(url) + response = self.client.get(url) # Then - self.assertEqual(response1.status_code, 404) - self.assertEqual(response2.status_code, 404) + self.assertEqual(response.status_code, 404) def test_metricity_voice_banned(self): cases = [ -- cgit v1.2.3 From cd797f801abaff8874e75b1ed093e17f67572a37 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Fri, 12 Mar 2021 16:00:14 +0200 Subject: Add case in query for voice chat activity --- pydis_site/apps/api/models/bot/metricity.py | 1 + 1 file changed, 1 insertion(+) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index db975d4e..5daa5c66 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -105,6 +105,7 @@ class Metricity: CASE WHEN channels.name ILIKE 'help-%%' THEN 'the help channels' WHEN channels.name ILIKE 'ot%%' THEN 'off-topic' + WHEN channels.name ILIKE '%%voice%%' THEN 'voice chats' ELSE channels.name END, COUNT(1) -- cgit v1.2.3