Skip to content
1 change: 1 addition & 0 deletions classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(
self.assignee = assignee
self.assignees = assignees or []
self.time_to_first_response = time_to_first_response
self.time_to_first_review = None
self.time_to_close = time_to_close
self.time_to_answer = time_to_answer
self.time_in_draft = time_in_draft
Expand Down
6 changes: 6 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class EnvVars:
hide_time_to_close (bool): If true, the time to close metric is hidden in the output
hide_time_to_first_response (bool): If true, the time to first response metric is hidden
in the output
hide_time_to_first_review (bool): If true, the time to first review metric is hidden in the output
hide_created_at (bool): If true, the created at timestamp is hidden in the output
hide_status (bool): If true, the status column is hidden in the output
ignore_users (List[str]): List of usernames to ignore when calculating metrics
Expand Down Expand Up @@ -79,6 +80,7 @@ def __init__(
hide_time_to_answer: bool,
hide_time_to_close: bool,
hide_time_to_first_response: bool,
hide_time_to_first_review: bool,
hide_created_at: bool,
hide_status: bool,
ignore_user: List[str],
Expand Down Expand Up @@ -114,6 +116,7 @@ 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.hide_time_to_first_review = hide_time_to_first_review
self.hide_created_at = hide_created_at
self.hide_status = hide_status
self.enable_mentor_count = enable_mentor_count
Expand Down Expand Up @@ -148,6 +151,7 @@ def __repr__(self):
f"{self.hide_time_to_answer}, "
f"{self.hide_time_to_close}, "
f"{self.hide_time_to_first_response}, "
f"{self.hide_time_to_first_review}, "
f"{self.hide_created_at}, "
f"{self.hide_status}, "
f"{self.ignore_users}, "
Expand Down Expand Up @@ -269,6 +273,7 @@ def get_env_vars(test: bool = False) -> EnvVars:
hide_time_to_answer = get_bool_env_var("HIDE_TIME_TO_ANSWER", False)
hide_time_to_close = get_bool_env_var("HIDE_TIME_TO_CLOSE", False)
hide_time_to_first_response = get_bool_env_var("HIDE_TIME_TO_FIRST_RESPONSE", False)
hide_time_to_first_review = get_bool_env_var("HIDE_TIME_TO_FIRST_REVIEW", 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)
Expand All @@ -293,6 +298,7 @@ def get_env_vars(test: bool = False) -> EnvVars:
hide_time_to_answer,
hide_time_to_close,
hide_time_to_first_response,
hide_time_to_first_review,
hide_created_at,
hide_status,
ignore_users_list,
Expand Down
18 changes: 17 additions & 1 deletion issue_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import github3
import github3.structs

from auth import auth_to_github, get_github_app_installation_token
from classes import IssueWithMetrics
from config import EnvVars, get_env_vars
Expand All @@ -39,6 +40,10 @@
get_stats_time_to_first_response,
measure_time_to_first_response,
)
from time_to_first_review import (
get_stats_time_to_first_review,
measure_time_to_first_review,
)
from time_to_merge import measure_time_to_merge
from time_to_ready_for_review import get_time_to_ready_for_review

Expand Down Expand Up @@ -159,7 +164,13 @@ def get_per_issue_metrics(
issue_with_metrics.pr_comment_count = count_pr_comments(
issue, pull_request, ignore_users
)

if not env_vars.hide_time_to_first_review and pull_request:
issue_with_metrics.time_to_first_review = measure_time_to_first_review(
issue,
pull_request,
ready_for_review_at,
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 @@ -305,6 +316,7 @@ def main(): # pragma: no cover
write_to_markdown(
issues_with_metrics=None,
average_time_to_first_response=None,
average_time_to_first_review=None,
average_time_to_close=None,
average_time_to_answer=None,
average_time_in_draft=None,
Expand Down Expand Up @@ -333,6 +345,7 @@ def main(): # pragma: no cover
write_to_markdown(
issues_with_metrics=None,
average_time_to_first_response=None,
average_time_to_first_review=None,
average_time_to_close=None,
average_time_to_answer=None,
average_time_in_draft=None,
Expand Down Expand Up @@ -365,6 +378,7 @@ def main(): # pragma: no cover
)

stats_time_to_first_response = get_stats_time_to_first_response(issues_with_metrics)
stats_time_to_first_review = get_stats_time_to_first_review(issues_with_metrics)
stats_time_to_close = None
if num_issues_closed > 0:
stats_time_to_close = get_stats_time_to_close(issues_with_metrics)
Expand All @@ -385,6 +399,7 @@ def main(): # pragma: no cover
write_to_json(
issues_with_metrics=issues_with_metrics,
stats_time_to_first_response=stats_time_to_first_response,
stats_time_to_first_review=stats_time_to_first_review,
stats_time_to_close=stats_time_to_close,
stats_time_to_answer=stats_time_to_answer,
stats_time_in_draft=stats_time_in_draft,
Expand All @@ -400,6 +415,7 @@ def main(): # pragma: no cover
write_to_markdown(
issues_with_metrics=issues_with_metrics,
average_time_to_first_response=stats_time_to_first_response,
average_time_to_first_review=stats_time_to_first_review,
average_time_to_close=stats_time_to_close,
average_time_to_answer=stats_time_to_answer,
average_time_in_draft=stats_time_in_draft,
Expand Down
15 changes: 15 additions & 0 deletions json_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
write_to_json(
issues_with_metrics: Union[List[IssueWithMetrics], None],
stats_time_to_first_response: Union[dict[str, timedelta], None],
stats_time_to_first_review: Union[dict[str, timedelta], None],
stats_time_to_close: Union[dict[str, timedelta], None],
stats_time_to_answer: Union[dict[str, timedelta], None],
stats_time_in_draft: Union[dict[str, timedelta], None],
Expand All @@ -29,6 +30,7 @@
def write_to_json(
issues_with_metrics: Union[List[IssueWithMetrics], None],
stats_time_to_first_response: Union[dict[str, timedelta], None],
stats_time_to_first_review: Union[dict[str, timedelta], None],
stats_time_to_close: Union[dict[str, timedelta], None],
stats_time_to_answer: Union[dict[str, timedelta], None],
stats_time_in_draft: Union[dict[str, timedelta], None],
Expand Down Expand Up @@ -104,6 +106,15 @@ def write_to_json(
med_time_to_first_response = stats_time_to_first_response["med"]
p90_time_to_first_response = stats_time_to_first_response["90p"]

# time to first review
average_time_to_first_review = None
med_time_to_first_review = None
p90_time_to_first_review = None
if stats_time_to_first_review is not None:
average_time_to_first_review = stats_time_to_first_review["avg"]
med_time_to_first_review = stats_time_to_first_review["med"]
p90_time_to_first_review = stats_time_to_first_review["90p"]

# time to close
average_time_to_close = None
med_time_to_close = None
Expand Down Expand Up @@ -155,16 +166,19 @@ def write_to_json(
# Create a dictionary with the metrics
metrics: dict[str, Any] = {
"average_time_to_first_response": str(average_time_to_first_response),
"average_time_to_first_review": str(average_time_to_first_review),
"average_time_to_close": str(average_time_to_close),
"average_time_to_answer": str(average_time_to_answer),
"average_time_in_draft": str(average_time_in_draft),
"average_time_in_labels": average_time_in_labels,
"median_time_to_first_response": str(med_time_to_first_response),
"median_time_to_first_review": str(med_time_to_first_review),
"median_time_to_close": str(med_time_to_close),
"median_time_to_answer": str(med_time_to_answer),
"median_time_in_draft": str(med_time_in_draft),
"median_time_in_labels": med_time_in_labels,
"90_percentile_time_to_first_response": str(p90_time_to_first_response),
"90_percentile_time_to_first_review": str(p90_time_to_first_review),
"90_percentile_time_to_close": str(p90_time_to_close),
"90_percentile_time_to_answer": str(p90_time_to_answer),
"90_percentile_time_in_draft": str(p90_time_in_draft),
Expand Down Expand Up @@ -193,6 +207,7 @@ def write_to_json(
"assignee": issue.assignee,
"assignees": issue.assignees,
"time_to_first_response": str(issue.time_to_first_response),
"time_to_first_review": str(issue.time_to_first_review),
"time_to_close": str(issue.time_to_close),
"time_to_answer": str(issue.time_to_answer),
"time_in_draft": str(issue.time_in_draft),
Expand Down
21 changes: 21 additions & 0 deletions markdown_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def get_non_hidden_columns(labels) -> List[str]:
if not hide_time_to_first_response:
columns.append("Time to first response")

hide_time_to_first_review = env_vars.hide_time_to_first_review
if not hide_time_to_first_review:
columns.append("Time to first review")

hide_time_to_close = env_vars.hide_time_to_close
if not hide_time_to_close:
columns.append("Time to close")
Expand Down Expand Up @@ -129,6 +133,7 @@ def sort_issues(
valid_fields = {
"time_to_close",
"time_to_first_response",
"time_to_first_review",
"time_to_answer",
"time_in_draft",
"created_at",
Expand Down Expand Up @@ -200,6 +205,7 @@ def group_issues(
def write_to_markdown(
issues_with_metrics: Union[List[IssueWithMetrics], None],
average_time_to_first_response: Union[dict[str, timedelta], None],
average_time_to_first_review: Union[dict[str, timedelta], None],
average_time_to_close: Union[dict[str, timedelta], None],
average_time_to_answer: Union[dict[str, timedelta], None],
average_time_in_draft: Union[dict[str, timedelta], None],
Expand Down Expand Up @@ -268,6 +274,7 @@ def write_to_markdown(
write_overall_metrics_tables(
issues_with_metrics,
average_time_to_first_response,
average_time_to_first_review,
average_time_to_close,
average_time_to_answer,
average_time_in_draft,
Expand Down Expand Up @@ -345,6 +352,8 @@ def write_to_markdown(
)
if "Time to first response" in columns:
file.write(f" {issue.time_to_first_response} |")
if "Time to first review" in columns:
file.write(f" {issue.time_to_first_review} |")
if "Time to close" in columns:
file.write(f" {issue.time_to_close} |")
if "Time to answer" in columns:
Expand Down Expand Up @@ -374,6 +383,7 @@ def write_to_markdown(
def write_overall_metrics_tables(
issues_with_metrics,
stats_time_to_first_response,
stats_time_to_first_review,
stats_time_to_close,
stats_time_to_answer,
average_time_in_draft,
Expand All @@ -397,6 +407,7 @@ def write_overall_metrics_tables(
column in columns
for column in [
"Time to first response",
"Time to first review",
"Time to close",
"Time to answer",
"Time in draft",
Expand All @@ -417,6 +428,16 @@ def write_overall_metrics_tables(
)
else:
file.write("| Time to first response | None | None | None |\n")
if "Time to first review" in columns:
if stats_time_to_first_review is not None:
file.write(
f"| Time to first review "
f"| {stats_time_to_first_review['avg']} "
f"| {stats_time_to_first_review['med']} "
f"| {stats_time_to_first_review['90p']} |\n"
)
else:
file.write("| Time to first review | None | None | None |\n")
if "Time to close" in columns:
if stats_time_to_close is not None:
file.write(
Expand Down
2 changes: 2 additions & 0 deletions test_assignee_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def test_assignee_in_markdown_output(self):
try:
write_to_markdown(
issues_with_metrics=issues_with_metrics,
average_time_to_first_review=None,
average_time_to_first_response={
"avg": timedelta(hours=3),
"med": timedelta(hours=3),
Expand Down Expand Up @@ -132,6 +133,7 @@ def test_assignee_in_json_output(self):
try:
json_output = write_to_json(
issues_with_metrics=issues_with_metrics,
stats_time_to_first_review=None,
stats_time_to_first_response={
"avg": timedelta(hours=3),
"med": timedelta(hours=3),
Expand Down
5 changes: 3 additions & 2 deletions test_column_order_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def test_status_and_created_at_columns_alignment(self):
write_to_markdown(
issues_with_metrics=issues_with_metrics,
average_time_to_first_response=None,
average_time_to_first_review=None,
average_time_to_close=None,
average_time_to_answer=None,
average_time_in_draft=None,
Expand All @@ -80,7 +81,7 @@ def test_status_and_created_at_columns_alignment(self):
# The table should have the columns in the correct order
# and the data should be properly aligned
expected_header = (
"| Title | URL | Assignee | Author | Time to first response | "
"| Title | URL | Assignee | Author | Time to first response | Time to first review | "
"Time to close | Time to answer | Created At | Status |"
)
self.assertIn(expected_header, content)
Expand All @@ -92,7 +93,7 @@ def test_status_and_created_at_columns_alignment(self):
"| Test Issue | https://github.com/user/repo/issues/1 | "
"[assignee1](https://github.com/assignee1) | "
"[testuser](https://github.com/testuser) | 1 day, 0:00:00 | "
"2 days, 0:00:00 | 3 days, 0:00:00 | 2023-01-01T00:00:00Z | open |"
"None | 2 days, 0:00:00 | 3 days, 0:00:00 | 2023-01-01T00:00:00Z | open |"
)
self.assertIn(expected_row, content)

Expand Down
4 changes: 4 additions & 0 deletions test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def test_get_env_vars_with_github_app(self):
hide_time_to_answer=False,
hide_time_to_close=False,
hide_time_to_first_response=False,
hide_time_to_first_review=False,
hide_created_at=True,
hide_status=True,
ignore_user=[],
Expand Down Expand Up @@ -187,6 +188,7 @@ def test_get_env_vars_with_token(self):
hide_time_to_answer=False,
hide_time_to_close=False,
hide_time_to_first_response=False,
hide_time_to_first_review=False,
hide_created_at=True,
hide_status=True,
ignore_user=[],
Expand Down Expand Up @@ -292,6 +294,7 @@ def test_get_env_vars_optional_values(self):
hide_time_to_answer=True,
hide_time_to_close=True,
hide_time_to_first_response=True,
hide_time_to_first_review=False,
hide_created_at=True,
hide_status=True,
ignore_user=[],
Expand Down Expand Up @@ -339,6 +342,7 @@ def test_get_env_vars_optionals_are_defaulted(self):
hide_time_to_answer=False,
hide_time_to_close=False,
hide_time_to_first_response=False,
hide_time_to_first_review=False,
hide_created_at=True,
hide_status=True,
ignore_user=[],
Expand Down
Loading