-
Notifications
You must be signed in to change notification settings - Fork 55
/
markdown_writer.py
280 lines (250 loc) · 11 KB
/
markdown_writer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
"""A module for writing GitHub issue metrics to a markdown file.
This module provides a function for writing a list of GitHub issues with metrics
to a markdown file. The metrics include the average time to first response, the
average time to close, and the average time to answer the discussions. The module
also provides functions for sorting the issues by time to first response.
Functions:
write_to_markdown(
issues_with_metrics: Union[List[IssueWithMetrics], None],
average_time_to_first_response: 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_labels: Union[dict, None],
num_issues_opened: Union[int, None],
num_issues_closed: Union[int, None],
num_mentor_count: Union[int, None],
labels: List[str],
search_query: str,
hide_label_metrics: bool,
hide_items_closed_count: bool,
non_mentioning_links: bool,
report_title: str,
output_file: str,
) -> None:
Write the issues with metrics to a markdown file.
get_non_hidden_columns(
average_time_to_first_response: timedelta,
average_time_to_close: timedelta,
average_time_to_answer: timedelta
) -> List[str]:
Get the columns that are not hidden.
"""
from datetime import timedelta
from typing import List, Union
from classes import IssueWithMetrics
from config import get_env_vars
def get_non_hidden_columns(labels) -> List[str]:
"""
Get a list of the columns that are not hidden.
Args:
labels (List[str]): A list of the labels that are used in the issues.
Returns:
List[str]: A list of the columns that are not hidden.
"""
columns = ["Title", "URL"]
env_vars = get_env_vars()
# Find the number of columns and which are to be hidden
hide_author = env_vars.hide_author
if not hide_author:
columns.append("Author")
hide_time_to_first_response = env_vars.hide_time_to_first_response
if not hide_time_to_first_response:
columns.append("Time to first response")
hide_time_to_close = env_vars.hide_time_to_close
if not hide_time_to_close:
columns.append("Time to close")
hide_time_to_answer = env_vars.hide_time_to_answer
if not hide_time_to_answer:
columns.append("Time to answer")
hide_label_metrics = env_vars.hide_label_metrics
if not hide_label_metrics and labels:
for label in labels:
columns.append(f"Time spent in {label}")
return columns
def write_to_markdown(
issues_with_metrics: Union[List[IssueWithMetrics], None],
average_time_to_first_response: 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_labels: Union[dict, None],
num_issues_opened: Union[int, None],
num_issues_closed: Union[int, None],
num_mentor_count: Union[int, None],
labels=None,
search_query=None,
hide_label_metrics=False,
hide_items_closed_count=False,
non_mentioning_links=False,
report_title="",
output_file="",
) -> None:
"""Write the issues with metrics to a markdown file.
Args:
issues_with_metrics (IssueWithMetrics): A list of GitHub issues with metrics
average_time_to_first_response (datetime.timedelta): The average time to first
response for the issues.
average_time_to_close (datetime.timedelta): The average time to close for the issues.
average_time_to_answer (datetime.timedelta): The average time to answer the discussions.
average_time_in_labels (dict): A dictionary containing the average time spent in each label.
file (file object, optional): The file object to write to. If not provided,
a file named "issue_metrics.md" will be created.
num_issues_opened (int): The Number of items that remain opened.
num_issues_closed (int): The number of issues that were closedi.
num_mentor_count (int): The number of very active commentors.
labels (List[str]): A list of the labels that are used in the issues.
search_query (str): The search query used to find the issues.
hide_label_metrics (bool): Represents whether the user has chosen to hide label metrics in the output
hide_items_closed_count (bool): Represents whether the user has chosen to hide the number of items closed
non_mentioning_links (bool): Represents whether links do not cause a notification in the desitnation repository
report_title (str): The title of the report
output_file (str): The name of the file to write the report to
Returns:
None.
"""
columns = get_non_hidden_columns(labels)
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")
# If all the metrics are None, then there are no issues
if not issues_with_metrics or len(issues_with_metrics) == 0:
file.write("no issues found for the given search criteria\n\n")
file.write(
"\n_This report was generated with the [Issue Metrics Action](https://github.com/github/issue-metrics)_\n"
)
if search_query:
file.write(f"Search query used to find these items: `{search_query}`\n")
return
# Write first table with overall metrics
write_overall_metrics_tables(
issues_with_metrics,
average_time_to_first_response,
average_time_to_close,
average_time_to_answer,
average_time_in_labels,
num_issues_opened,
num_issues_closed,
num_mentor_count,
labels,
columns,
file,
hide_label_metrics,
hide_items_closed_count,
)
# Write second table with individual issue/pr/discussion metrics
# First write the header
file.write("|")
for column in columns:
file.write(f" {column} |")
file.write("\n")
# Then write the column dividers
file.write("|")
for _ in columns:
file.write(" --- |")
file.write("\n")
# Then write the issues/pr/discussions row by row
for issue in issues_with_metrics:
# Replace the vertical bar with the HTML entity
issue.title = issue.title.replace("|", "|")
# Replace any whitespace
issue.title = issue.title.strip()
if non_mentioning_links:
file.write(
f"| {issue.title} | "
f"{issue.html_url.replace('https://github.com', 'https://www.github.com')} |"
)
else:
file.write(f"| {issue.title} | " f"{issue.html_url} |")
if "Author" in columns:
file.write(f" [{issue.author}](https://github.com/{issue.author}) |")
if "Time to first response" in columns:
file.write(f" {issue.time_to_first_response} |")
if "Time to close" in columns:
file.write(f" {issue.time_to_close} |")
if "Time to answer" in columns:
file.write(f" {issue.time_to_answer} |")
if labels and issue.label_metrics:
for label in labels:
if f"Time spent in {label}" in columns:
file.write(f" {issue.label_metrics[label]} |")
file.write("\n")
file.write(
"\n_This report was generated with the [Issue Metrics Action](https://github.com/github/issue-metrics)_\n"
)
if search_query:
file.write(f"Search query used to find these items: `{search_query}`\n")
print("Wrote issue metrics to issue_metrics.md")
def write_overall_metrics_tables(
issues_with_metrics,
stats_time_to_first_response,
stats_time_to_close,
stats_time_to_answer,
stats_time_in_labels,
num_issues_opened,
num_issues_closed,
num_mentor_count,
labels,
columns,
file,
hide_label_metrics,
hide_items_closed_count=False,
):
"""Write the overall metrics tables to the markdown file."""
if (
"Time to first response" in columns
or "Time to close" in columns
or "Time to answer" in columns
or (hide_label_metrics is False and len(labels) > 0)
):
file.write("| Metric | Average | Median | 90th percentile |\n")
file.write("| --- | --- | --- | ---: |\n")
if "Time to first response" in columns:
if stats_time_to_first_response is not None:
file.write(
f"| Time to first response "
f"| {stats_time_to_first_response['avg']} "
f"| {stats_time_to_first_response['med']} "
f"| {stats_time_to_first_response['90p']} |\n"
)
else:
file.write("| Time to first response | None | None | None |\n")
if "Time to close" in columns:
if stats_time_to_close is not None:
file.write(
f"| Time to close "
f"| {stats_time_to_close['avg']} "
f"| {stats_time_to_close['med']} "
f"| {stats_time_to_close['90p']} |\n"
)
else:
file.write("| Time to close | None | None | None |\n")
if "Time to answer" in columns:
if stats_time_to_answer is not None:
file.write(
f"| Time to answer "
f"| {stats_time_to_answer['avg']} "
f"| {stats_time_to_answer['med']} "
f"| {stats_time_to_answer['90p']} |\n"
)
else:
file.write("| Time to answer | None | None | None |\n")
if labels and stats_time_in_labels:
for label in labels:
if (
f"Time spent in {label}" in columns
and label in stats_time_in_labels["avg"]
):
file.write(
f"| Time spent in {label} "
f"| {stats_time_in_labels['avg'][label]} "
f"| {stats_time_in_labels['med'][label]} "
f"| {stats_time_in_labels['90p'][label]} |\n"
)
file.write("\n")
# Write count stats to a separate table
file.write("| Metric | Count |\n")
file.write("| --- | ---: |\n")
file.write(f"| Number of items that remain open | {num_issues_opened} |\n")
if not hide_items_closed_count:
file.write(f"| Number of items closed | {num_issues_closed} |\n")
file.write(f"| Number of most active mentors | {num_mentor_count} |\n")
file.write(f"| Total number of items created | {len(issues_with_metrics)} |\n\n")