Skip to content

Commit be2f85c

Browse files
Copilotzkoppert
andcommitted
Add assignee support to issue metrics reporting
Co-authored-by: zkoppert <[email protected]>
1 parent d4a3747 commit be2f85c

9 files changed

+180
-12
lines changed

classes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class IssueWithMetrics:
1313
title (str): The title of the issue.
1414
html_url (str): The URL of the issue on GitHub.
1515
author (str): The author of the issue.
16+
assignee (str, optional): The primary assignee of the issue.
17+
assignees (list, optional): All assignees of the issue.
1618
time_to_first_response (timedelta, optional): The time it took to
1719
get the first response to the issue.
1820
time_to_close (timedelta, optional): The time it took to close the issue.
@@ -38,10 +40,14 @@ def __init__(
3840
labels_metrics=None,
3941
mentor_activity=None,
4042
created_at=None,
43+
assignee=None,
44+
assignees=None,
4145
):
4246
self.title = title
4347
self.html_url = html_url
4448
self.author = author
49+
self.assignee = assignee
50+
self.assignees = assignees or []
4551
self.time_to_first_response = time_to_first_response
4652
self.time_to_close = time_to_close
4753
self.time_to_answer = time_to_answer

config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class EnvVars:
3030
authentication
3131
gh_token (str | None): GitHub personal access token (PAT) for API authentication
3232
ghe (str): The GitHub Enterprise URL to use for authentication
33+
hide_assignee (bool): If true, the assignee's information is hidden in the output
3334
hide_author (bool): If true, the author's information is hidden in the output
3435
hide_items_closed_count (bool): If true, the number of items closed metric is hidden
3536
in the output
@@ -64,6 +65,7 @@ def __init__(
6465
gh_app_enterprise_only: bool,
6566
gh_token: str | None,
6667
ghe: str | None,
68+
hide_assignee: bool,
6769
hide_author: bool,
6870
hide_items_closed_count: bool,
6971
hide_label_metrics: bool,
@@ -92,6 +94,7 @@ def __init__(
9294
self.ghe = ghe
9395
self.ignore_users = ignore_user
9496
self.labels_to_measure = labels_to_measure
97+
self.hide_assignee = hide_assignee
9598
self.hide_author = hide_author
9699
self.hide_items_closed_count = hide_items_closed_count
97100
self.hide_label_metrics = hide_label_metrics
@@ -119,6 +122,7 @@ def __repr__(self):
119122
f"{self.gh_app_enterprise_only},"
120123
f"{self.gh_token},"
121124
f"{self.ghe},"
125+
f"{self.hide_assignee},"
122126
f"{self.hide_author},"
123127
f"{self.hide_items_closed_count}),"
124128
f"{self.hide_label_metrics},"
@@ -226,6 +230,7 @@ def get_env_vars(test: bool = False) -> EnvVars:
226230
draft_pr_tracking = get_bool_env_var("DRAFT_PR_TRACKING", False)
227231

228232
# Hidden columns
233+
hide_assignee = get_bool_env_var("HIDE_ASSIGNEE", False)
229234
hide_author = get_bool_env_var("HIDE_AUTHOR", False)
230235
hide_items_closed_count = get_bool_env_var("HIDE_ITEMS_CLOSED_COUNT", False)
231236
hide_label_metrics = get_bool_env_var("HIDE_LABEL_METRICS", False)
@@ -246,6 +251,7 @@ def get_env_vars(test: bool = False) -> EnvVars:
246251
gh_app_enterprise_only,
247252
gh_token,
248253
ghe,
254+
hide_assignee,
249255
hide_author,
250256
hide_items_closed_count,
251257
hide_label_metrics,

issue_metrics.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ def get_per_issue_metrics(
8585
None,
8686
None,
8787
)
88+
# Discussions typically don't have assignees in the same way as issues/PRs
89+
issue_with_metrics.assignee = None
90+
issue_with_metrics.assignees = []
8891
if env_vars.hide_time_to_first_response is False:
8992
issue_with_metrics.time_to_first_response = (
9093
measure_time_to_first_response(None, issue, ignore_users)
@@ -119,6 +122,20 @@ def get_per_issue_metrics(
119122
author=issue.user["login"], # type: ignore
120123
)
121124

125+
# Extract assignee information from the issue
126+
issue_dict = issue.issue.as_dict() # type: ignore
127+
assignee = None
128+
assignees = []
129+
130+
if issue_dict.get("assignee"):
131+
assignee = issue_dict["assignee"]["login"]
132+
133+
if issue_dict.get("assignees"):
134+
assignees = [a["login"] for a in issue_dict["assignees"]]
135+
136+
issue_with_metrics.assignee = assignee
137+
issue_with_metrics.assignees = assignees
138+
122139
# Check if issue is actually a pull request
123140
pull_request, ready_for_review_at = None, None
124141
if issue.issue.pull_request_urls: # type: ignore

json_writer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ def write_to_json(
177177
"title": issue.title,
178178
"html_url": issue.html_url,
179179
"author": issue.author,
180+
"assignee": issue.assignee,
181+
"assignees": issue.assignees,
180182
"time_to_first_response": str(issue.time_to_first_response),
181183
"time_to_close": str(issue.time_to_close),
182184
"time_to_answer": str(issue.time_to_answer),

markdown_writer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ def get_non_hidden_columns(labels) -> List[str]:
5555
env_vars = get_env_vars()
5656

5757
# Find the number of columns and which are to be hidden
58+
hide_assignee = env_vars.hide_assignee
59+
if not hide_assignee:
60+
columns.append("Assignee")
61+
5862
hide_author = env_vars.hide_author
5963
if not hide_author:
6064
columns.append("Author")
@@ -203,6 +207,11 @@ def write_to_markdown(
203207
)
204208
else:
205209
file.write(f"| {issue.title} | {issue.html_url} |")
210+
if "Assignee" in columns:
211+
if issue.assignee:
212+
file.write(f" [{issue.assignee}](https://{endpoint}/{issue.assignee}) |")
213+
else:
214+
file.write(" None |")
206215
if "Author" in columns:
207216
file.write(f" [{issue.author}](https://{endpoint}/{issue.author}) |")
208217
if "Time to first response" in columns:

test_assignee_functionality.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Test assignee functionality added to issue metrics."""
2+
3+
import os
4+
import unittest
5+
from unittest.mock import patch
6+
from markdown_writer import get_non_hidden_columns
7+
8+
9+
class TestAssigneeFunctionality(unittest.TestCase):
10+
"""Test suite for the assignee functionality."""
11+
12+
@patch.dict(
13+
os.environ,
14+
{
15+
"GH_TOKEN": "test_token",
16+
"SEARCH_QUERY": "is:issue is:open repo:user/repo",
17+
"HIDE_ASSIGNEE": "false",
18+
"HIDE_AUTHOR": "false",
19+
},
20+
clear=True,
21+
)
22+
def test_get_non_hidden_columns_includes_assignee_by_default(self):
23+
"""Test that assignee column is included by default."""
24+
columns = get_non_hidden_columns(labels=None)
25+
self.assertIn("Assignee", columns)
26+
self.assertIn("Author", columns)
27+
28+
@patch.dict(
29+
os.environ,
30+
{
31+
"GH_TOKEN": "test_token",
32+
"SEARCH_QUERY": "is:issue is:open repo:user/repo",
33+
"HIDE_ASSIGNEE": "true",
34+
"HIDE_AUTHOR": "false",
35+
},
36+
clear=True,
37+
)
38+
def test_get_non_hidden_columns_hides_assignee_when_env_set(self):
39+
"""Test that assignee column is hidden when HIDE_ASSIGNEE is true."""
40+
columns = get_non_hidden_columns(labels=None)
41+
self.assertNotIn("Assignee", columns)
42+
self.assertIn("Author", columns)
43+
44+
@patch.dict(
45+
os.environ,
46+
{
47+
"GH_TOKEN": "test_token",
48+
"SEARCH_QUERY": "is:issue is:open repo:user/repo",
49+
"HIDE_ASSIGNEE": "false",
50+
"HIDE_AUTHOR": "true",
51+
},
52+
clear=True,
53+
)
54+
def test_get_non_hidden_columns_shows_assignee_but_hides_author(self):
55+
"""Test that assignee can be shown while author is hidden."""
56+
columns = get_non_hidden_columns(labels=None)
57+
self.assertIn("Assignee", columns)
58+
self.assertNotIn("Author", columns)
59+
60+
@patch.dict(
61+
os.environ,
62+
{
63+
"GH_TOKEN": "test_token",
64+
"SEARCH_QUERY": "is:issue is:open repo:user/repo",
65+
"HIDE_ASSIGNEE": "true",
66+
"HIDE_AUTHOR": "true",
67+
},
68+
clear=True,
69+
)
70+
def test_get_non_hidden_columns_hides_both_assignee_and_author(self):
71+
"""Test that both assignee and author can be hidden."""
72+
columns = get_non_hidden_columns(labels=None)
73+
self.assertNotIn("Assignee", columns)
74+
self.assertNotIn("Author", columns)
75+
76+
def test_assignee_column_position(self):
77+
"""Test that assignee column appears before author column."""
78+
with patch.dict(
79+
os.environ,
80+
{
81+
"GH_TOKEN": "test_token",
82+
"SEARCH_QUERY": "is:issue is:open repo:user/repo",
83+
"HIDE_ASSIGNEE": "false",
84+
"HIDE_AUTHOR": "false",
85+
},
86+
clear=True,
87+
):
88+
columns = get_non_hidden_columns(labels=None)
89+
assignee_index = columns.index("Assignee")
90+
author_index = columns.index("Author")
91+
self.assertLess(assignee_index, author_index,
92+
"Assignee column should appear before Author column")
93+
94+
95+
if __name__ == "__main__":
96+
unittest.main()

test_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def test_get_env_vars_with_github_app(self):
123123
gh_app_enterprise_only=False,
124124
gh_token="",
125125
ghe="",
126+
hide_assignee=False,
126127
hide_author=False,
127128
hide_items_closed_count=False,
128129
hide_label_metrics=False,
@@ -177,6 +178,7 @@ def test_get_env_vars_with_token(self):
177178
gh_app_enterprise_only=False,
178179
gh_token=TOKEN,
179180
ghe="",
181+
hide_assignee=False,
180182
hide_author=False,
181183
hide_items_closed_count=False,
182184
hide_label_metrics=False,
@@ -266,6 +268,7 @@ def test_get_env_vars_optional_values(self):
266268
gh_app_enterprise_only=False,
267269
gh_token=TOKEN,
268270
ghe="",
271+
hide_assignee=False,
269272
hide_author=True,
270273
hide_items_closed_count=True,
271274
hide_label_metrics=True,
@@ -309,6 +312,7 @@ def test_get_env_vars_optionals_are_defaulted(self):
309312
gh_app_enterprise_only=False,
310313
gh_token="TOKEN",
311314
ghe="",
315+
hide_assignee=False,
312316
hide_author=False,
313317
hide_items_closed_count=False,
314318
hide_label_metrics=False,

test_json_writer.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def test_write_to_json(self):
2121
title="Issue 1",
2222
html_url="https://github.com/owner/repo/issues/1",
2323
author="alice",
24+
assignee="charlie",
25+
assignees=["charlie"],
2426
time_to_first_response=timedelta(days=3),
2527
time_to_close=timedelta(days=6),
2628
time_to_answer=None,
@@ -34,6 +36,8 @@ def test_write_to_json(self):
3436
title="Issue 2",
3537
html_url="https://github.com/owner/repo/issues/2",
3638
author="bob",
39+
assignee=None,
40+
assignees=[],
3741
time_to_first_response=timedelta(days=2),
3842
time_to_close=timedelta(days=4),
3943
time_to_answer=timedelta(days=1),
@@ -96,6 +100,8 @@ def test_write_to_json(self):
96100
"title": "Issue 1",
97101
"html_url": "https://github.com/owner/repo/issues/1",
98102
"author": "alice",
103+
"assignee": "charlie",
104+
"assignees": ["charlie"],
99105
"time_to_first_response": "3 days, 0:00:00",
100106
"time_to_close": "6 days, 0:00:00",
101107
"time_to_answer": "None",
@@ -107,6 +113,8 @@ def test_write_to_json(self):
107113
"title": "Issue 2",
108114
"html_url": "https://github.com/owner/repo/issues/2",
109115
"author": "bob",
116+
"assignee": None,
117+
"assignees": [],
110118
"time_to_first_response": "2 days, 0:00:00",
111119
"time_to_close": "4 days, 0:00:00",
112120
"time_to_answer": "1 day, 0:00:00",
@@ -143,6 +151,8 @@ def test_write_to_json_with_no_response(self):
143151
title="Issue 1",
144152
html_url="https://github.com/owner/repo/issues/1",
145153
author="alice",
154+
assignee=None,
155+
assignees=[],
146156
time_to_first_response=None,
147157
time_to_close=None,
148158
time_to_answer=None,
@@ -153,6 +163,8 @@ def test_write_to_json_with_no_response(self):
153163
title="Issue 2",
154164
html_url="https://github.com/owner/repo/issues/2",
155165
author="bob",
166+
assignee=None,
167+
assignees=[],
156168
time_to_first_response=None,
157169
time_to_close=None,
158170
time_to_answer=None,
@@ -199,6 +211,8 @@ def test_write_to_json_with_no_response(self):
199211
"title": "Issue 1",
200212
"html_url": "https://github.com/owner/repo/issues/1",
201213
"author": "alice",
214+
"assignee": None,
215+
"assignees": [],
202216
"time_to_first_response": "None",
203217
"time_to_close": "None",
204218
"time_to_answer": "None",
@@ -210,6 +224,8 @@ def test_write_to_json_with_no_response(self):
210224
"title": "Issue 2",
211225
"html_url": "https://github.com/owner/repo/issues/2",
212226
"author": "bob",
227+
"assignee": None,
228+
"assignees": [],
213229
"time_to_first_response": "None",
214230
"time_to_close": "None",
215231
"time_to_answer": "None",

0 commit comments

Comments
 (0)