Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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