Skip to content

Commit

Permalink
First stab at figuring out the number of very active mentors in a pro…
Browse files Browse the repository at this point in the history
…ject.
  • Loading branch information
MaineC committed Feb 13, 2024
1 parent 5499c18 commit 8b788f0
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 0 deletions.
149 changes: 149 additions & 0 deletions most_active_mentors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""A module for measuring the number of very active mentors
This module provides functions for measuring the number of active mentors on a project.
This is measured by number of PR comments. We are working under the assumption that PR
comments are left in good faith to move contributors further instead of nitpicking and
dis-couraging them.
Open questions:
- should there be an option to limit this to certain users, e.g. core maintainers?
- should there be a limit to how many comments per PR we consider to avoid having
the statistic dominated by contested PRs?
- should this metric count consecutive comments coming from the same user as only
one to avoid people unnessesarily splitting their comments to game the metric?
- instead of PR comments should we count PRs on which a username was seen as commenter?
Functions:
collect_response_usernames(
issue: Union[github3.issues.Issue, None],
discussion: Union[dict, None],
pull_request: Union[github3.pulls.PullRequest, None],
max_comments_to_evaluate,
) -> ____________
Collect the number of responses per username for single item. Take only top n
comments (max_comments_to_evaluate) into consideration.
get_number_of_active_reviewers(
mentors: List [mentors with metrics)
) -> int active_number
Count the number of mentors active at least n times
"""
from datetime import datetime, timedelta
from typing import List, Union

import github3
import numpy

from classes import IssueWithMetrics


def count_comments_per_user(
issue: Union[github3.issues.Issue, None], # type: ignore
discussion: Union[dict, None] = None,
pull_request: Union[github3.pulls.PullRequest, None] = None,
ready_for_review_at: Union[datetime, None] = None,
ignore_users: List[str] = None,
max_comments_to_eval = 20,
) -> dict:
"""Count the number of times a user was seen commenting on a single item.
Args:
issue (Union[github3.issues.Issue, None]): A GitHub issue.
discussion (Union[dict, None]): A GitHub discussion.
pull_request (Union[github3.pulls.PullRequest, None]): A GitHub pull request.
ignore_users (List[str]): A list of GitHub usernames to ignore.
max_comments_to_eval: Maximum number of comments per item to look at.
Returns:
dict: A dictionary of usernames seen and number of comments they left.
"""
if ignore_users is None:
ignore_users = []
mentor_count = {}

# Get the first comments
if issue:
comments = issue.issue.comments(
number=max_comments_to_eval, sort="created", direction="asc"
) # type: ignore
for comment in comments:
if ignore_comment(
issue.issue.user,
comment.user,
ignore_users,
comment.created_at,
ready_for_review_at,
):
continue
# increase the number of comments left by current user by 1
if (comment.user.login in mentor_count):
mentor_count[comment.user.login] += 1
else:
mentor_count[comment.user.login] = 1

# Check if the issue is actually a pull request
# so we may also get the first review comment time
if pull_request:
review_comments = pull_request.reviews(number=max_comments_to_eval) # type: ignore
for review_comment in review_comments:
if ignore_comment(
issue.issue.user,
review_comment.user,
ignore_users,
review_comment.submitted_at,
ready_for_review_at,
):
continue

# increase the number of comments left by current user by 1
if (review_comment.user.login in mentor_count):
mentor_count[review_comment.user.login] += 1
else:
mentor_count[review_comment.user.login] = 1

return mentor_count


def ignore_comment(
issue_user: github3.users.User,
comment_user: github3.users.User,
ignore_users: List[str],
comment_created_at: datetime,
ready_for_review_at: Union[datetime, None],
) -> bool:
"""Check if a comment should be ignored."""
return (
# ignore comments by IGNORE_USERS
comment_user.login in ignore_users
# ignore comments by bots
or comment_user.type == "Bot"
# ignore comments by the issue creator
or comment_user.login == issue_user.login
# ignore comments created before the issue was ready for review
or (ready_for_review_at and comment_created_at < ready_for_review_at)
)


def get_mentor_count(
mentor_activity: dict,
cutoff: int
) -> int:
""" Calculate the number of active mentors on the project.
Args:
mentor_activity (dict: A dictionary with usernames to count of comments left.
cutoff (int: the minimum number of comments a user has to leave to count as active mentor.)
Returns:
int: Number of active mentors
"""
active_mentor_count = 0
for mentor, count in mentor_activity.items():
if (count >= cutoff):
active_mentor_count += 1

return active_mentor_count

54 changes: 54 additions & 0 deletions test_most_active_mentors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""A module containing unit tests for the most_active_mentors module.
This module contains unit tests for the count_comments_per_user and
get_mentor_count functions in the most_active_mentors module.
The tests use mock GitHub issues and comments to test the functions' behavior.
Classes:
TestCountCommentsPerUser: A class to test the count_comments_per_user function.
TestGetMentorCount: A class to test the
get_mentor_count function.
"""
import unittest
from datetime import datetime, timedelta
from unittest.mock import MagicMock

from classes import IssueWithMetrics
from most_active_mentors import (
count_comments_per_user,
get_mentor_count,
)


class TestCountCommentsPerUser(unittest.TestCase):
"""Test the count_comments_per_user function."""

def test_count_comments_per_user(self):
"""Test that count_comments_per_user correctly counts user comments.
This test mocks the GitHub connection and issue comments, and checks that
count_comments_per_user correctly considers user comments for counting.
"""
# Set up the mock GitHub issues
mock_issue1 = MagicMock()
mock_issue1.comments = 2
mock_issue1.issue.user.login = "issue_owner"
mock_issue1.created_at = "2023-01-01T00:00:00Z"

# Set up 21 mock GitHub issue comments - only 20 should be counted
mock_issue1.issue.comments.return_value = []
for i in range(22):
mock_comment1 = MagicMock()
mock_comment1.user.login = "very_active_user"
mock_comment1.created_at = datetime.fromisoformat(f"2023-01-02T{i:02d}:00:00Z")
mock_issue1.issue.comments.return_value.append(mock_comment1)

# Call the function
result = count_comments_per_user(mock_issue1)
expected_result = {"very_active_user": 20}

# Check the results
self.assertEqual(result, expected_result)

0 comments on commit 8b788f0

Please sign in to comment.