Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe
| `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. |
| `HIDE_STATUS` | False | True | If set to `true`, the status column will not be shown |
| `HIDE_CREATED_AT` | False | True | If set to `true`, the creation timestamp will not be displayed in the generated Markdown file. |
| `HIDE_PR_STATISTICS` | False | True | If set to `true`, PR comment statistics (mean, median, 90th percentile, and individual PR comment counts) will not be displayed in the generated Markdown file. |
| `DRAFT_PR_TRACKING` | False | False | If set to `true`, draft PRs will be included in the metrics as a new column and in the summary stats. |
| `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]'`) Users in this list will also have their authored issues and pull requests removed from the Markdown table. |
| `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 |
Expand Down
3 changes: 3 additions & 0 deletions classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class IssueWithMetrics:
mentor_activity (dict, optional): A dictionary containing active mentors
created_at (datetime, optional): The time the issue was created.
status (str, optional): The status of the issue, e.g., "open", "closed as completed",
pr_comment_count (int, optional): The number of comments on the PR (excluding bots).
"""

# pylint: disable=too-many-instance-attributes
Expand All @@ -44,6 +45,7 @@ def __init__(
assignee=None,
assignees=None,
status=None,
pr_comment_count=None,
):
self.title = title
self.html_url = html_url
Expand All @@ -58,3 +60,4 @@ def __init__(
self.mentor_activity = mentor_activity
self.created_at = created_at
self.status = status
self.pr_comment_count = pr_comment_count
6 changes: 6 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class EnvVars:
rate_limit_bypass (bool): If set to TRUE, bypass the rate limit for the GitHub API
draft_pr_tracking (bool): If set to TRUE, track PR time in draft state
in addition to other metrics
hide_pr_statistics (bool): If set to TRUE, hide PR comment statistics in the output
"""

def __init__(
Expand Down Expand Up @@ -88,6 +89,7 @@ def __init__(
output_file: str,
rate_limit_bypass: bool = False,
draft_pr_tracking: bool = False,
hide_pr_statistics: bool = True,
):
self.gh_app_id = gh_app_id
self.gh_app_installation_id = gh_app_installation_id
Expand Down Expand Up @@ -116,6 +118,7 @@ def __init__(
self.output_file = output_file
self.rate_limit_bypass = rate_limit_bypass
self.draft_pr_tracking = draft_pr_tracking
self.hide_pr_statistics = hide_pr_statistics

def __repr__(self):
return (
Expand Down Expand Up @@ -147,6 +150,7 @@ def __repr__(self):
f"{self.output_file}"
f"{self.rate_limit_bypass}"
f"{self.draft_pr_tracking}"
f"{self.hide_pr_statistics}"
)


Expand Down Expand Up @@ -244,6 +248,7 @@ def get_env_vars(test: bool = False) -> EnvVars:
hide_time_to_first_response = get_bool_env_var("HIDE_TIME_TO_FIRST_RESPONSE", False)
hide_created_at = get_bool_env_var("HIDE_CREATED_AT", True)
hide_status = get_bool_env_var("HIDE_STATUS", True)
hide_pr_statistics = get_bool_env_var("HIDE_PR_STATISTICS", True)
enable_mentor_count = get_bool_env_var("ENABLE_MENTOR_COUNT", False)
min_mentor_comments = os.getenv("MIN_MENTOR_COMMENTS", "10")
max_comments_eval = os.getenv("MAX_COMMENTS_EVAL", "20")
Expand Down Expand Up @@ -278,4 +283,5 @@ def get_env_vars(test: bool = False) -> EnvVars:
output_file,
rate_limit_bypass,
draft_pr_tracking,
hide_pr_statistics,
)
12 changes: 12 additions & 0 deletions issue_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from markdown_helpers import markdown_too_large_for_issue_body, split_markdown_file
from markdown_writer import write_to_markdown
from most_active_mentors import count_comments_per_user, get_mentor_count
from pr_comments import count_pr_comments, get_stats_pr_comments
from search import get_owners_and_repositories, search_issues
from time_in_draft import get_stats_time_in_draft, measure_time_in_draft
from time_to_answer import get_stats_time_to_answer, measure_time_to_answer
Expand Down Expand Up @@ -153,6 +154,12 @@ def get_per_issue_metrics(
f"An error occurred processing review comments. Perhaps the review contains a ghost user. {e}"
)

# Count PR comments if this is a pull request and statistics are not hidden
if pull_request and not env_vars.hide_pr_statistics:
issue_with_metrics.pr_comment_count = count_pr_comments(
issue, pull_request, ignore_users
)

if env_vars.hide_time_to_first_response is False:
issue_with_metrics.time_to_first_response = (
measure_time_to_first_response(
Expand Down Expand Up @@ -302,6 +309,7 @@ def main(): # pragma: no cover
average_time_to_answer=None,
average_time_in_draft=None,
average_time_in_labels=None,
stats_pr_comments=None,
num_issues_opened=None,
num_issues_closed=None,
num_mentor_count=None,
Expand Down Expand Up @@ -329,6 +337,7 @@ def main(): # pragma: no cover
average_time_to_answer=None,
average_time_in_draft=None,
average_time_in_labels=None,
stats_pr_comments=None,
num_issues_opened=None,
num_issues_closed=None,
num_mentor_count=None,
Expand Down Expand Up @@ -362,6 +371,7 @@ def main(): # pragma: no cover

stats_time_to_answer = get_stats_time_to_answer(issues_with_metrics)
stats_time_in_draft = get_stats_time_in_draft(issues_with_metrics)
stats_pr_comments = get_stats_pr_comments(issues_with_metrics)

num_mentor_count = 0
if enable_mentor_count:
Expand All @@ -379,6 +389,7 @@ def main(): # pragma: no cover
stats_time_to_answer=stats_time_to_answer,
stats_time_in_draft=stats_time_in_draft,
stats_time_in_labels=stats_time_in_labels,
stats_pr_comments=stats_pr_comments,
num_issues_opened=num_issues_open,
num_issues_closed=num_issues_closed,
num_mentor_count=num_mentor_count,
Expand All @@ -393,6 +404,7 @@ def main(): # pragma: no cover
average_time_to_answer=stats_time_to_answer,
average_time_in_draft=stats_time_in_draft,
average_time_in_labels=stats_time_in_labels,
stats_pr_comments=stats_pr_comments,
num_issues_opened=num_issues_open,
num_issues_closed=num_issues_closed,
num_mentor_count=num_mentor_count,
Expand Down
16 changes: 15 additions & 1 deletion json_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import json
import os
from datetime import timedelta
from typing import Any, List, Union
from typing import Any, Dict, List, Union

from classes import IssueWithMetrics

Expand All @@ -33,6 +33,7 @@ def write_to_json(
stats_time_to_answer: Union[dict[str, timedelta], None],
stats_time_in_draft: Union[dict[str, timedelta], None],
stats_time_in_labels: Union[dict[str, dict[str, timedelta]], None],
stats_pr_comments: Union[Dict[str, float], None],
num_issues_opened: Union[int, None],
num_issues_closed: Union[int, None],
num_mentor_count: Union[int, None],
Expand Down Expand Up @@ -142,6 +143,15 @@ def write_to_json(
for label, time in stats_time_in_labels["90p"].items():
p90_time_in_labels[label] = str(time)

# PR comments statistics
average_pr_comments = None
med_pr_comments = None
p90_pr_comments = None
if stats_pr_comments is not None:
average_pr_comments = stats_pr_comments["avg"]
med_pr_comments = stats_pr_comments["med"]
p90_pr_comments = stats_pr_comments["90p"]

# Create a dictionary with the metrics
metrics: dict[str, Any] = {
"average_time_to_first_response": str(average_time_to_first_response),
Expand All @@ -159,6 +169,9 @@ def write_to_json(
"90_percentile_time_to_answer": str(p90_time_to_answer),
"90_percentile_time_in_draft": str(p90_time_in_draft),
"90_percentile_time_in_labels": p90_time_in_labels,
"average_pr_comments": average_pr_comments,
"median_pr_comments": med_pr_comments,
"90_percentile_pr_comments": p90_pr_comments,
"num_items_opened": num_issues_opened,
"num_items_closed": num_issues_closed,
"num_mentor_count": num_mentor_count,
Expand All @@ -184,6 +197,7 @@ def write_to_json(
"time_to_answer": str(issue.time_to_answer),
"time_in_draft": str(issue.time_in_draft),
"label_metrics": formatted_label_metrics,
"pr_comment_count": issue.pr_comment_count,
"created_at": str(issue.created_at),
}
)
Expand Down
45 changes: 36 additions & 9 deletions markdown_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ def get_non_hidden_columns(labels) -> List[str]:
if not hide_status:
columns.append("Status")

hide_pr_statistics = env_vars.hide_pr_statistics
if not hide_pr_statistics:
columns.append("PR Comments")

return columns


Expand All @@ -101,6 +105,7 @@ def write_to_markdown(
average_time_to_answer: Union[dict[str, timedelta], None],
average_time_in_draft: Union[dict[str, timedelta], None],
average_time_in_labels: Union[dict, None],
stats_pr_comments: Union[dict[str, float], None],
num_issues_opened: Union[int, None],
num_issues_closed: Union[int, None],
num_mentor_count: Union[int, None],
Expand Down Expand Up @@ -146,6 +151,7 @@ def write_to_markdown(

"""
columns = get_non_hidden_columns(labels)
env_vars = get_env_vars()
output_file_name = output_file if output_file else "issue_metrics.md"
with open(output_file_name, "w", encoding="utf-8") as file:
file.write(f"# {report_title}\n\n")
Expand All @@ -169,6 +175,7 @@ def write_to_markdown(
average_time_to_answer,
average_time_in_draft,
average_time_in_labels,
stats_pr_comments,
num_issues_opened,
num_issues_closed,
num_mentor_count,
Expand All @@ -178,6 +185,7 @@ def write_to_markdown(
hide_label_metrics,
hide_items_closed_count,
enable_mentor_count,
env_vars.hide_pr_statistics,
)

# Write second table with individual issue/pr/discussion metrics
Expand Down Expand Up @@ -238,6 +246,8 @@ def write_to_markdown(
file.write(f" {issue.created_at} |")
if "Status" in columns:
file.write(f" {issue.status} |")
if "PR Comments" in columns:
file.write(f" {issue.pr_comment_count or 'N/A'} |")
file.write("\n")
file.write(
"\n_This report was generated with the \
Expand All @@ -256,6 +266,7 @@ def write_overall_metrics_tables(
stats_time_to_answer,
average_time_in_draft,
stats_time_in_labels,
stats_pr_comments,
num_issues_opened,
num_issues_closed,
num_mentor_count,
Expand All @@ -265,17 +276,23 @@ def write_overall_metrics_tables(
hide_label_metrics,
hide_items_closed_count=False,
enable_mentor_count=False,
hide_pr_statistics=True,
):
"""Write the overall metrics tables to the markdown file."""
if any(
column in columns
for column in [
"Time to first response",
"Time to close",
"Time to answer",
"Time in draft",
]
) or (hide_label_metrics is False and len(labels) > 0):

if (
any(
column in columns
for column in [
"Time to first response",
"Time to close",
"Time to answer",
"Time in draft",
]
)
or (hide_label_metrics is False and len(labels) > 0)
or (not hide_pr_statistics and stats_pr_comments is not None)
):
file.write("| Metric | Average | Median | 90th percentile |\n")
file.write("| --- | --- | --- | ---: |\n")
if "Time to first response" in columns:
Expand Down Expand Up @@ -330,6 +347,16 @@ def write_overall_metrics_tables(
f"| {stats_time_in_labels['med'][label]} "
f"| {stats_time_in_labels['90p'][label]} |\n"
)

# Add PR comment statistics if not hidden
if not hide_pr_statistics and stats_pr_comments is not None:
file.write(
f"| Number of comments per PR "
f"| {stats_pr_comments['avg']} "
f"| {stats_pr_comments['med']} "
f"| {stats_pr_comments['90p']} |\n"
)

if "Status" in columns: # Add logic for the 'status' column
file.write("| Status | | | |\n")

Expand Down
Loading
Loading