Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First stab at figuring out the number of very active mentors #206

Merged
merged 19 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ This action can be configured to authenticate with GitHub App Installation or Pe
| `HIDE_TIME_TO_CLOSE` | False | False | If set to `true`, the time to close will not be displayed in the generated Markdown file. |
| `HIDE_TIME_TO_FIRST_RESPONSE` | False | False | If set to `true`, the time to first response will not be displayed in the generated Markdown file. |
| `IGNORE_USERS` | False | False | A comma separated list of users to ignore when calculating metrics. (ie. `IGNORE_USERS: 'user1,user2'`). To ignore bots, append `[bot]` to the user (ie. `IGNORE_USERS: 'github-actions[bot]'`) |
| `ENABLE_MENTOR_COUNT` | False | False | If set to 'TRUE' count number of comments users left on discussions, issues and PRs and display number of active mentors |
| `MIN_MENTOR_COMMENTS` | False | 10 | Minimum number of comments to count as a mentor |
| `MAX_COMMENTS_EVAL` | False | 20 | Maximum number of comments per thread to evaluate for mentor stats |
| `HEAVILY_INVOLVED_CUTOFF` | False | 3 | Cutoff after which a mentor's comments in one issue are no longer counted against their total score |
| `LABELS_TO_MEASURE` | False | `""` | A comma separated list of labels to measure how much time the label is applied. If not provided, no labels durations will be measured. Not compatible with discussions at this time. |
| `SEARCH_QUERY` | True | `""` | The query by which you can filter issues/PRs which must contain a `repo:`, `org:`, `owner:`, or a `user:` entry. For discussions, include `type:discussions` in the query. |

Expand Down
5 changes: 5 additions & 0 deletions classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ class IssueWithMetrics:
time_to_answer (timedelta, optional): The time it took to answer the
discussions in the issue.
label_metrics (dict, optional): A dictionary containing the label metrics
mentor_activity (dict, optional): A dictionary containing active mentors

"""

# pylint: disable=too-many-instance-attributes

def __init__(
self,
title,
Expand All @@ -31,6 +34,7 @@ def __init__(
time_to_close=None,
time_to_answer=None,
labels_metrics=None,
mentor_activity=None,
):
self.title = title
self.html_url = html_url
Expand All @@ -39,3 +43,4 @@ def __init__(
self.time_to_close = time_to_close
self.time_to_answer = time_to_answer
self.label_metrics = labels_metrics
self.mentor_activity = mentor_activity
25 changes: 25 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class EnvVars:
hide_time_to_first_response (bool): If true, the time to first response metric is hidden in the output
ignore_users (List[str]): List of usernames to ignore when calculating metrics
labels_to_measure (List[str]): List of labels to measure how much time the lable is applied
enable_mentor_count (str): If set to TRUE, compute number of mentors
jmeridth marked this conversation as resolved.
Show resolved Hide resolved
min_mentor_comments (str): If set, defines the minimum number of comments for mentors
max_comments_eval (str): If set, defines the maximum number of comments to look at for mentor evaluation
heavily_involved_cutoff (str): If set, defines the cutoff after which heavily involved commentors in
search_query (str): Search query used to filter issues/prs/discussions on GitHub
"""

Expand All @@ -51,11 +55,16 @@ def __init__(
hide_time_to_first_response: bool,
ignore_user: List[str],
labels_to_measure: List[str],
enable_mentor_count: str,
jmeridth marked this conversation as resolved.
Show resolved Hide resolved
min_mentor_comments: str,
max_comments_eval: str,
heavily_involved_cutoff: str,
search_query: str,
):
self.gh_app_id = gh_app_id
self.gh_app_installation_id = gh_app_installation_id
self.gh_app_private_key_bytes = gh_app_private_key_bytes
self.search_query = search_query
jmeridth marked this conversation as resolved.
Show resolved Hide resolved
self.gh_token = gh_token
self.ghe = ghe
self.ignore_users = ignore_user
Expand All @@ -65,6 +74,10 @@ def __init__(
self.hide_time_to_answer = hide_time_to_answer
self.hide_time_to_close = hide_time_to_close
self.hide_time_to_first_response = hide_time_to_first_response
self.enable_mentor_count = enable_mentor_count
self.min_mentor_comments = min_mentor_comments
self.max_comments_eval = max_comments_eval
self.heavily_involved_cutoff = heavily_involved_cutoff
self.search_query = search_query

def __repr__(self):
Expand All @@ -82,6 +95,10 @@ def __repr__(self):
f"{self.hide_time_to_first_response},"
f"{self.ignore_users},"
f"{self.labels_to_measure},"
f"{self.enable_mentor_count},"
f"{self.min_mentor_comments},"
f"{self.max_comments_eval},"
f"{self.heavily_involved_cutoff},"
f"{self.search_query})"
)

Expand Down Expand Up @@ -166,6 +183,10 @@ def get_env_vars(test: bool = False) -> EnvVars:
hide_time_to_answer = get_bool_env_var("HIDE_TIME_TO_ANSWER")
hide_time_to_close = get_bool_env_var("HIDE_TIME_TO_CLOSE")
hide_time_to_first_response = get_bool_env_var("HIDE_TIME_TO_FIRST_RESPONSE")
enable_mentor_count = os.getenv("ENABLE_MENTOR_COUNT", "FALSE")
MaineC marked this conversation as resolved.
Show resolved Hide resolved
min_mentor_comments = os.getenv("MIN_MENTOR_COMMENTS", "10")
max_comments_eval = os.getenv("MAX_COMMENTS_EVAL", "20")
heavily_involved_cutoff = os.getenv("HEAVILY_INVOLVED_CUTOFF", "3")

return EnvVars(
gh_app_id,
Expand All @@ -180,5 +201,9 @@ def get_env_vars(test: bool = False) -> EnvVars:
hide_time_to_first_response,
ignore_users_list,
labels_to_measure_list,
enable_mentor_count,
min_mentor_comments,
max_comments_eval,
heavily_involved_cutoff,
search_query,
)
40 changes: 36 additions & 4 deletions issue_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from json_writer import write_to_json
from labels import get_label_metrics, get_stats_time_in_labels
from markdown_writer import write_to_markdown
from most_active_mentors import count_comments_per_user, get_mentor_count
from time_to_answer import get_stats_time_to_answer, measure_time_to_answer
from time_to_close import get_stats_time_to_close, measure_time_to_close
from time_to_first_response import (
Expand All @@ -40,8 +41,6 @@
from time_to_merge import measure_time_to_merge
from time_to_ready_for_review import get_time_to_ready_for_review

GITHUB_BASE_URL = "https://github.com"


def search_issues(
search_query: str, github_connection: github3.GitHub
Expand Down Expand Up @@ -126,6 +125,8 @@ def get_per_issue_metrics(
discussions: bool = False,
labels: Union[List[str], None] = None,
ignore_users: Union[List[str], None] = None,
max_comments_to_eval: int = 20,
heavily_involved: int = 3,
) -> tuple[List, int, int]:
"""
Calculate the metrics for each issue/pr/discussion in a list provided.
Expand Down Expand Up @@ -158,10 +159,20 @@ def get_per_issue_metrics(
None,
None,
None,
None,
)
issue_with_metrics.time_to_first_response = measure_time_to_first_response(
None, issue, ignore_users
)
issue_with_metrics.mentor_activity = count_comments_per_user(
None,
issue,
ignore_users,
None,
None,
max_comments_to_eval,
heavily_involved,
)
issue_with_metrics.time_to_answer = measure_time_to_answer(issue)
if issue["closedAt"]:
issue_with_metrics.time_to_close = measure_time_to_close(None, issue)
Expand All @@ -188,6 +199,15 @@ def get_per_issue_metrics(
issue_with_metrics.time_to_first_response = measure_time_to_first_response(
issue, None, pull_request, ready_for_review_at, ignore_users
)
issue_with_metrics.mentor_activity = count_comments_per_user(
issue,
None,
pull_request,
ready_for_review_at,
ignore_users,
max_comments_to_eval,
heavily_involved,
)
if labels:
issue_with_metrics.label_metrics = get_label_metrics(issue, labels)
if issue.state == "closed": # type: ignore
Expand Down Expand Up @@ -259,6 +279,10 @@ def main():
token,
env_vars.ghe,
)
enable_mentor_count = env_vars.enable_mentor_count
min_mentor_count = int(env_vars.min_mentor_comments)
max_comments_eval = int(env_vars.max_comments_eval)
heavily_involved_cutoff = int(env_vars.heavily_involved_cutoff)

# Get the repository owner and name from the search query
owner = get_owner(search_query)
Expand All @@ -283,13 +307,13 @@ def main():
issues = get_discussions(token, search_query)
if len(issues) <= 0:
print("No discussions found")
write_to_markdown(None, None, None, None, None, None, None)
write_to_markdown(None, None, None, None, None, None, None, None)
return
else:
issues = search_issues(search_query, github_connection)
if len(issues) <= 0:
print("No issues found")
write_to_markdown(None, None, None, None, None, None, None)
write_to_markdown(None, None, None, None, None, None, None, None)
return

# Get all the metrics
Expand All @@ -298,6 +322,8 @@ def main():
discussions="type:discussions" in search_query,
labels=labels,
ignore_users=ignore_users,
max_comments_to_eval=max_comments_eval,
heavily_involved=heavily_involved_cutoff,
)

stats_time_to_first_response = get_stats_time_to_first_response(issues_with_metrics)
Expand All @@ -307,6 +333,10 @@ def main():

stats_time_to_answer = get_stats_time_to_answer(issues_with_metrics)

num_mentor_count = 0
if enable_mentor_count == "TRUE":
MaineC marked this conversation as resolved.
Show resolved Hide resolved
num_mentor_count = get_mentor_count(issues_with_metrics, min_mentor_count)

# Get stats describing the time in label for each label and store it in a dictionary
# where the key is the label and the value is the average time
stats_time_in_labels = get_stats_time_in_labels(issues_with_metrics, labels)
Expand All @@ -320,6 +350,7 @@ def main():
stats_time_in_labels,
num_issues_open,
num_issues_closed,
num_mentor_count,
search_query,
)
write_to_markdown(
Expand All @@ -330,6 +361,7 @@ def main():
stats_time_in_labels,
num_issues_open,
num_issues_closed,
num_mentor_count,
labels,
search_query,
)
Expand Down
3 changes: 3 additions & 0 deletions json_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def write_to_json(
stats_time_in_labels: Union[dict[str, dict[str, timedelta]], None],
num_issues_opened: Union[int, None],
num_issues_closed: Union[int, None],
num_mentor_count: Union[int, None],
search_query: str,
) -> str:
"""
Expand All @@ -42,6 +43,7 @@ def write_to_json(
"average_time_to_answer": "1 day, 0:00:00",
"num_items_opened": 2,
"num_items_closed": 1,
"num_mentor_count": 5,
"total_item_count": 2,
"issues": [
{
Expand Down Expand Up @@ -129,6 +131,7 @@ def write_to_json(
"90_percentile_time_in_labels": p90_time_in_labels,
"num_items_opened": num_issues_opened,
"num_items_closed": num_issues_closed,
"num_mentor_count": num_mentor_count,
"total_item_count": len(issues_with_metrics),
}

Expand Down
8 changes: 7 additions & 1 deletion markdown_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
average_time_to_answer: timedelta,
num_issues_opened: int,
num_issues_closed: int,
num_mentor_count: int,
file: file object = None
) -> None:
Write the issues with metrics to a markdown file.
Expand Down Expand Up @@ -79,6 +80,7 @@ def write_to_markdown(
average_time_in_labels: Union[dict, None],
num_issues_opened: Union[int, None],
num_issues_closed: Union[int, None],
num_mentor_count: Union[int, None],
labels=None,
search_query=None,
hide_label_metrics=False,
Expand All @@ -95,7 +97,8 @@ def write_to_markdown(
file (file object, optional): The file object to write to. If not provided,
a file named "issue_metrics.md" will be created.
num_issues_opened (int): The Number of items that remain opened.
num_issues_closed (int): The number of issues that were closed.
num_issues_closed (int): The number of issues that were closedi.
num_mentor_count (int): The number of very active commentors.
labels (List[str]): A list of the labels that are used in the issues.
search_query (str): The search query used to find the issues.
hide_label_metrics (bool): Represents whether the user has chosen to hide label metrics in the output
Expand Down Expand Up @@ -127,6 +130,7 @@ def write_to_markdown(
average_time_in_labels,
num_issues_opened,
num_issues_closed,
num_mentor_count,
labels,
columns,
file,
Expand Down Expand Up @@ -184,6 +188,7 @@ def write_overall_metrics_tables(
stats_time_in_labels,
num_issues_opened,
num_issues_closed,
num_mentor_count,
labels,
columns,
file,
Expand Down Expand Up @@ -246,4 +251,5 @@ def write_overall_metrics_tables(
file.write("| --- | ---: |\n")
file.write(f"| Number of items that remain open | {num_issues_opened} |\n")
file.write(f"| Number of items closed | {num_issues_closed} |\n")
file.write(f"| Number of most active mentors | {num_mentor_count} |\n")
file.write(f"| Total number of items created | {len(issues_with_metrics)} |\n\n")
Loading