Skip to content

Commit

Permalink
Merge pull request #451 from python-discord/mbaruh/channel-activity
Browse files Browse the repository at this point in the history
Add route to get a member's data for helper review
  • Loading branch information
mbaruh committed Mar 16, 2021
2 parents 49c7afa + d213474 commit 8276ba5
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 4 deletions.
109 changes: 108 additions & 1 deletion postgres/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,63 @@ INSERT INTO users VALUES (
current_timestamp
);

INSERT INTO users VALUES (
1,
current_timestamp
);

CREATE TABLE channels (
id varchar,
name varchar,
primary key(id)
);

INSERT INTO channels VALUES(
'267659945086812160',
'python-general'
);

INSERT INTO channels VALUES(
'11',
'help-apple'
);

INSERT INTO channels VALUES(
'12',
'help-cherry'
);

INSERT INTO channels VALUES(
'21',
'ot0-hello'
);

INSERT INTO channels VALUES(
'22',
'ot1-world'
);

INSERT INTO channels VALUES(
'31',
'voice-chat-0'
);

INSERT INTO channels VALUES(
'32',
'code-help-voice-0'
);

INSERT INTO channels VALUES(
'1234',
'zebra'
);

CREATE TABLE messages (
id varchar,
author_id varchar references users(id),
is_deleted boolean,
created_at timestamp,
channel_id varchar,
channel_id varchar references channels(id),
primary key(id)
);

Expand All @@ -37,3 +88,59 @@ INSERT INTO messages VALUES(
now() + INTERVAL '10 minutes,',
'1234'
);

INSERT INTO messages VALUES(
2,
0,
false,
now(),
'11'
);

INSERT INTO messages VALUES(
3,
0,
false,
now(),
'12'
);

INSERT INTO messages VALUES(
4,
1,
false,
now(),
'21'
);

INSERT INTO messages VALUES(
5,
1,
false,
now(),
'22'
);

INSERT INTO messages VALUES(
6,
1,
false,
now(),
'31'
);

INSERT INTO messages VALUES(
7,
1,
false,
now(),
'32'
);

INSERT INTO messages VALUES(
8,
1,
true,
now(),
'32'
);
41 changes: 41 additions & 0 deletions pydis_site/apps/api/models/bot/metricity.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import List, Tuple

from django.db import connections

BLOCK_INTERVAL = 10 * 60 # 10 minute blocks
Expand Down Expand Up @@ -89,3 +91,42 @@ def total_message_blocks(self, user_id: str) -> int:
raise NotFound()

return values[0]

def top_channel_activity(self, user_id: str) -> List[Tuple[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'
WHEN channels.name ILIKE '%%voice%%' THEN 'voice chats'
ELSE channels.name
END,
COUNT(1)
FROM
messages
LEFT JOIN channels ON channels.id = messages.channel_id
WHERE
author_id = '%s' AND NOT messages.is_deleted
GROUP BY
1
ORDER BY
2 DESC
LIMIT
3;
""",
[user_id]
)

values = self.cursor.fetchall()

if not values:
raise NotFound()

return values
39 changes: 36 additions & 3 deletions pydis_site/apps/api/tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def test_get_metricity_data(self):
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')
Expand All @@ -436,13 +436,24 @@ def test_no_metricity_user(self):
# 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')
response = self.client.get(url)

# Then
self.assertEqual(response.status_code, 404)

def test_metricity_voice_banned(self):
cases = [
{'exception': None, 'voice_banned': True},
{'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']):
Expand All @@ -455,14 +466,35 @@ def test_metricity_voice_banned(self):
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)
self.metricity = self.metricity.return_value.__enter__.return_value
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")
Expand All @@ -472,3 +504,4 @@ def mock_no_metricity_user(self):
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()
31 changes: 31 additions & 0 deletions pydis_site/apps/api/viewsets/bot/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<snowflake:int>/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.
Expand Down Expand Up @@ -262,3 +278,18 @@ def metricity_data(self, request: Request, pk: str = None) -> Response:
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)

0 comments on commit 8276ba5

Please sign in to comment.