Skip to content

Commit 92b849a

Browse files
authored
Merge pull request #78 from Okabe-Junya/junya/feat/add-ignore-users
feat: Filter Out CI User Responses from Time to First Response
2 parents 7296d9d + fd029d1 commit 92b849a

5 files changed

+77
-8
lines changed

.env-example

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
GH_TOKEN = " "
22
SEARCH_QUERY = "repo:owner/repo is:open is:issue"
33
LABELS_TO_MEASURE = "waiting-for-review,waiting-for-manager"
4+
IGNORE_USERS = "user1,user2"

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Below are the allowed configuration options:
5252
| `HIDE_TIME_TO_CLOSE` | False | | If set to any value, the time to close will not be displayed in the generated markdown file. |
5353
| `HIDE_TIME_TO_ANSWER` | False | | If set to any value, the time to answer a discussion will not be displayed in the generated markdown file. |
5454
| `HIDE_LABEL_METRICS` | False | | If set to any value, the time in label metrics will not be displayed in the generated markdown file. |
55+
| `IGNORE_USERS` | False | | A comma separated list of users to ignore when calculating metrics. (ie. `IGNORE_USERS: 'user1,user2'`) |
5556

5657
### Example workflows
5758

@@ -425,4 +426,4 @@ jobs:
425426

426427
## License
427428

428-
[MIT](LICENSE)
429+
[MIT](LICENSE)

issue_metrics.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Searches for issues in a GitHub repository that match the given search query.
1414
auth_to_github() -> github3.GitHub: Connect to GitHub API with token authentication.
1515
get_per_issue_metrics(issues: Union[List[dict], List[github3.issues.Issue]],
16-
discussions: bool = False) -> tuple[List, int, int]:
16+
discussions: bool = False), labels: Union[List[str], None] = None, ignore_users: List[str] = [] -> tuple[List, int, int]:
1717
Calculate the metrics for each issue in a list of GitHub issues.
1818
get_owner(search_query: str) -> Union[str, None]]:
1919
Get the owner from the search query.
@@ -41,13 +41,14 @@
4141
)
4242

4343

44-
def get_env_vars() -> tuple[str, str]:
44+
def get_env_vars() -> tuple[str, str, List[str]]:
4545
"""
4646
Get the environment variables for use in the script.
4747
4848
Returns:
4949
str: the search query used to filter issues, prs, and discussions
5050
str: the github token used to authenticate to github.com
51+
List[str]: a list of users to ignore when calculating metrics
5152
"""
5253
search_query = os.getenv("SEARCH_QUERY")
5354
if not search_query:
@@ -57,7 +58,13 @@ def get_env_vars() -> tuple[str, str]:
5758
if not token:
5859
raise ValueError("GITHUB_TOKEN environment variable not set")
5960

60-
return search_query, token
61+
ignore_users = os.getenv("IGNORE_USERS")
62+
if ignore_users:
63+
ignore_users = ignore_users.split(",")
64+
else:
65+
ignore_users = []
66+
67+
return search_query, token, ignore_users
6168

6269

6370
def search_issues(
@@ -125,6 +132,7 @@ def get_per_issue_metrics(
125132
issues: Union[List[dict], List[github3.search.IssueSearchResult]], # type: ignore
126133
discussions: bool = False,
127134
labels: Union[List[str], None] = None,
135+
ignore_users: List[str] = [],
128136
) -> tuple[List, int, int]:
129137
"""
130138
Calculate the metrics for each issue/pr/discussion in a list provided.
@@ -135,6 +143,7 @@ def get_per_issue_metrics(
135143
discussions (bool, optional): Whether the issues are discussions or not.
136144
Defaults to False.
137145
labels (List[str]): A list of labels to measure time spent in. Defaults to empty list.
146+
ignore_users (List[str]): A list of users to ignore when calculating metrics.
138147
139148
Returns:
140149
tuple[List[IssueWithMetrics], int, int]: A tuple containing a
@@ -157,7 +166,7 @@ def get_per_issue_metrics(
157166
None,
158167
)
159168
issue_with_metrics.time_to_first_response = measure_time_to_first_response(
160-
None, issue
169+
None, issue, ignore_users
161170
)
162171
issue_with_metrics.time_to_answer = measure_time_to_answer(issue)
163172
if issue["closedAt"]:
@@ -175,7 +184,7 @@ def get_per_issue_metrics(
175184
None,
176185
)
177186
issue_with_metrics.time_to_first_response = measure_time_to_first_response(
178-
issue, None
187+
issue, None, ignore_users
179188
)
180189
if labels:
181190
issue_with_metrics.label_metrics = get_label_metrics(issue, labels)
@@ -238,6 +247,7 @@ def main():
238247
env_vars = get_env_vars()
239248
search_query = env_vars[0]
240249
token = env_vars[1]
250+
ignore_users = env_vars[2]
241251

242252
# Get the repository owner and name from the search query
243253
owner = get_owner(search_query)
@@ -280,6 +290,7 @@ def main():
280290
issues,
281291
discussions="type:discussions" in search_query,
282292
labels=labels,
293+
ignore_users=ignore_users,
283294
)
284295

285296
average_time_to_first_response = get_average_time_to_first_response(

test_time_to_first_response.py

+50
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,56 @@ def test_measure_time_to_first_response_no_comments(self):
6161
# Check the results
6262
self.assertEqual(result, expected_result)
6363

64+
def test_measure_time_to_first_response_ignore_users(self):
65+
"""Test that measure_time_to_first_response ignores comments from ignored users."""
66+
# Set up the mock GitHub issues
67+
mock_issue1 = MagicMock()
68+
mock_issue1.comments = 1
69+
mock_issue1.created_at = "2023-01-01T00:00:00Z"
70+
71+
# Set up the mock GitHub comments (one ignored, one not ignored)
72+
mock_comment1 = MagicMock()
73+
mock_comment1.user.login = "ignored_user"
74+
mock_comment1.created_at = datetime.fromisoformat("2023-01-02T00:00:00Z")
75+
76+
mock_comment2 = MagicMock()
77+
mock_comment2.user.login = "not_ignored_user"
78+
mock_comment2.created_at = datetime.fromisoformat("2023-01-03T00:00:00Z")
79+
80+
mock_issue1.issue.comments.return_value = [mock_comment1, mock_comment2]
81+
82+
# Call the function
83+
result = measure_time_to_first_response(mock_issue1, None, ["ignored_user"])
84+
expected_result = timedelta(days=2)
85+
86+
# Check the results
87+
self.assertEqual(result, expected_result)
88+
89+
def test_measure_time_to_first_response_only_ignored_users(self):
90+
"""Test that measure_time_to_first_response returns empty for an issue with only ignored users."""
91+
# Set up the mock GitHub issues
92+
mock_issue1 = MagicMock()
93+
mock_issue1.comments = 1
94+
mock_issue1.created_at = "2023-01-01T00:00:00Z"
95+
96+
# Set up the mock GitHub comments (all ignored)
97+
mock_comment1 = MagicMock()
98+
mock_comment1.user.login = "ignored_user"
99+
mock_comment1.created_at = datetime.fromisoformat("2023-01-02T00:00:00Z")
100+
101+
mock_comment2 = MagicMock()
102+
mock_comment2.user.login = "ignored_user2"
103+
mock_comment2.created_at = datetime.fromisoformat("2023-01-03T00:00:00Z")
104+
105+
mock_issue1.issue.comments.return_value = [mock_comment1, mock_comment2]
106+
107+
# Call the function
108+
result = measure_time_to_first_response(mock_issue1, None, ["ignored_user", "ignored_user2"])
109+
expected_result = None
110+
111+
# Check the results
112+
self.assertEqual(result, expected_result)
113+
64114

65115
class TestGetAverageTimeToFirstResponse(unittest.TestCase):
66116
"""Test the get_average_time_to_first_response function."""

time_to_first_response.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
def measure_time_to_first_response(
2828
issue: Union[github3.issues.Issue, None], # type: ignore
2929
discussion: Union[dict, None],
30+
ignore_users: List[str] = [],
3031
) -> Union[timedelta, None]:
3132
"""Measure the time to first response for a single issue or a discussion.
3233
3334
Args:
3435
issue (Union[github3.issues.Issue, None]): A GitHub issue.
3536
discussion (Union[dict, None]): A GitHub discussion.
37+
ignore_users (List[str]): A list of GitHub usernames to ignore.
3638
3739
Returns:
3840
Union[timedelta, None]: The time to first response for the issue/discussion.
@@ -46,17 +48,21 @@ def measure_time_to_first_response(
4648
# Get the first comment time
4749
if issue:
4850
comments = issue.issue.comments(
49-
number=1, sort="created", direction="asc"
51+
number=20, sort="created", direction="asc"
5052
) # type: ignore
5153
for comment in comments:
54+
if comment.user.login in ignore_users:
55+
continue
5256
first_comment_time = comment.created_at
5357

5458
# Check if the issue is actually a pull request
5559
# so we may also get the first review comment time
5660
if issue.issue.pull_request_urls:
5761
pull_request = issue.issue.pull_request()
58-
review_comments = pull_request.reviews(number=1) # type: ignore
62+
review_comments = pull_request.reviews(number=50) # type: ignore
5963
for review_comment in review_comments:
64+
if review_comment.user.login in ignore_users:
65+
continue
6066
first_review_comment_time = review_comment.submitted_at
6167

6268
# Figure out the earliest response timestamp

0 commit comments

Comments
 (0)