Skip to content

Commit 973de7e

Browse files
benedikt-voelkelBenedikt Volkel
andauthored
[AsyncSW] Update fetching of PRs (#1596)
* report all PRs that are marked with "async-*" labels * sort first by * merged * closed (not merged) * open * sort then by label * add README.md Co-authored-by: Benedikt Volkel <benedikt.volkel@cern.ch>
1 parent 281b243 commit 973de7e

File tree

2 files changed

+95
-57
lines changed

2 files changed

+95
-57
lines changed

UTILS/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# General utilities
2+
3+
## Fetching PRs (based on labels)
4+
5+
The tool [`o2dpg_make_github_pr_report.py`](o2dpg_make_github_pr_report.py) can be used to create a markdown report PRs in a given repository.
6+
Note that PRs are fetched and reported based on assigned labels. By default, the report tool will look for labels of the form `async-*`.
7+
PRs are fetched and sorted first by their state, closed and merged, just closed and open.
8+
Within that sorting, they are grouped by assigned labels and the last sorting is based on time. The time-based sorting is based on
9+
10+
* state `closed` and `merged`: `merged_at`,
11+
* state `closed` and not `merged`: `updated_at`,
12+
* state `open`: `updated_at.`
13+
14+
To fetch for instance PRs for `O2`, the following command would do the job and it will write the markdown report to `o2dpg_pr_report_O2DPG.md`:
15+
```bash
16+
./UTILS/o2dpg_make_github_pr_report.py --repo O2DPG
17+
```
18+
19+
A few more things can be configured. To see the full list of options and flags, type
20+
```bash
21+
./UTILS/o2dpg_make_github_pr_report.py --help
22+
```

UTILS/o2dpg_make_github_pr_report.py

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,38 @@ def organise_prs(prs):
1717
prs_merged = []
1818
# collect the time of merged PRs
1919
merged_at = []
20-
# other PRs, open, closed and not merged
21-
prs_other = []
20+
# simply closed
21+
prs_closed = []
22+
closed_updated_at = []
23+
# open PRs
24+
prs_open = []
25+
open_updated_at = []
2226

2327
for pr in prs:
2428
if not pr['merged_at']:
25-
# that has not been merged
26-
prs_other.append(pr)
27-
continue
29+
if pr['state'] == 'open':
30+
prs_open.append(pr)
31+
open_updated_at.append(pr['updated_at'])
32+
continue
33+
if pr['state'] == 'closed':
34+
prs_closed.append(pr)
35+
closed_updated_at.append(pr['updated_at'])
36+
continue
2837
# get the PR itself and the merged timestamp
2938
prs_merged.append(pr)
3039
merged_at.append(pr['merged_at'])
3140

3241
# sort the merged PRs by their merged timestamp
3342
prs_merged = [pr for _, pr in sorted(zip(merged_at, prs_merged))]
43+
prs_closed = [pr for _, pr in sorted(zip(closed_updated_at, prs_closed))]
44+
prs_open = [pr for _, pr in sorted(zip(open_updated_at, prs_open))]
3445

35-
return prs_merged, prs_other
46+
return {'merged': prs_merged,
47+
'closed': prs_closed,
48+
'open': prs_open}
3649

3750

38-
def get_prs(owner, repo, request_labels, pr_state, per_page=50, start_page=1, pages=1):
51+
def get_prs(owner, repo, request_labels, pr_state=None, per_page=50, start_page=1, pages=1):
3952
"""
4053
Get PRs according to some selection
4154
"""
@@ -44,7 +57,8 @@ def get_prs(owner, repo, request_labels, pr_state, per_page=50, start_page=1, pa
4457

4558
has_error = False
4659
for page in range(start_page, pages + 1):
47-
url = f'https://api.github.com/repos/{owner}/{repo}/pulls?state={pr_state}&page={page}&per_page={per_page}'
60+
pr_state = f'state={pr_state}&' if pr_state else 'state=all&'
61+
url = f'https://api.github.com/repos/{owner}/{repo}/pulls?{pr_state}page={page}&per_page={per_page}'
4862

4963
# Send GET request to GitHub API
5064
response = requests.get(url)
@@ -73,7 +87,7 @@ def get_prs(owner, repo, request_labels, pr_state, per_page=50, start_page=1, pa
7387
break
7488

7589
if has_error:
76-
return None, None
90+
return None
7791

7892
# organise PRs into different lists (merged and others)
7993
return organise_prs(prs_return)
@@ -115,59 +129,61 @@ def separate_labels_request_accept(labels, accept_suffix=None):
115129
return labels_request, labels_accept
116130

117131

118-
def make_report(prs_merged, prs_other, repo, labels_request, label_accept_suffix, outfile):
132+
def make_report(all_prs, repo, labels_request, label_accept_suffix, outfile=None):
119133
"""
120134
Make a report
121135
122136
The report consists of one table per label which will be written to a text file.
123137
"""
124138
# common header for each single table
125-
common_header = '| Requestor | Package | PR | PR title | Merged at | Data or MC |\n| --- | --- | --- | --- | --- | --- |\n'
126-
rows_per_label = {label: [] for label in labels_request}
139+
common_header = '| Requestor | Package | PR | PR title | State | Merged at | Data or MC |\n| --- | --- | --- | --- | --- | --- | --- |\n'
140+
141+
if not outfile:
142+
outfile = f'o2dpg_pr_report_{repo}.md'
127143

128144
with open(outfile, 'w') as f:
129145

130-
f.write(f'Merged PRs: {len(prs_merged)}\nOther closed PRs: {len(prs_other)}\nLabels: {", ".join(labels_request)}\n\n')
131-
f.write('# List PRs from oldest to recent (merged)\n')
146+
f.write(f'# List PRs for {repo} (from oldest to recent)\n')
132147

133148
# first put the merged PRs
134-
for pr in prs_merged:
135-
mc_data = []
136-
# collect the labels for which table this PR should be taken into account
137-
labels_take = []
138-
139-
for label in pr['labels']:
140-
label_name = label['name']
141-
if label_name.lower() in ('mc', 'data'):
142-
# get assigned MC or DATA label if this PR has it
143-
mc_data.append(label['name'])
144-
if label_name in labels_request and (not label_accept_suffix or f'{label_name}-{label_accept_suffix}' not in pr['labels']):
145-
# check if that label is one that flags a request. If at the same time there is also the corresponding accepted label, don't take this PR into account for the report.
146-
labels_take.append(label_name)
147-
148-
if not labels_take:
149-
# no labels of interest
150-
continue
149+
for key, prs in all_prs.items():
151150

152-
# if no specific MC or DATA label, assume valid for both
153-
mc_data = ','.join(mc_data) if mc_data else 'MC,DATA'
154-
for label in labels_take:
155-
rows_per_label[label].append(f'| {pr["user"]["login"]} | {repo} | [PR]({pr["html_url"]}) | {pr["title"]} | {pr["merged_at"]} | {mc_data} |\n')
151+
rows_per_label = {label: [] for label in labels_request}
156152

157-
for label, rows in rows_per_label.items():
158-
if not rows:
159-
# nothing to add here
160-
continue
161-
f.write(f'\n==> START label {label} <==\n')
162-
f.write(common_header)
163-
for row in rows:
164-
f.write(row)
165-
f.write(f'==> END label {label} <==\n')
153+
f.write(f'\n\n## PRs in state {key}')
154+
for pr in prs:
155+
mc_data = []
156+
# collect the labels for which table this PR should be taken into account
157+
labels_take = []
158+
159+
for label in pr['labels']:
160+
label_name = label['name']
161+
if label_name.lower() in ('mc', 'data'):
162+
# get assigned MC or DATA label if this PR has it
163+
mc_data.append(label['name'])
164+
if label_name in labels_request and (not label_accept_suffix or f'{label_name}-{label_accept_suffix}' not in pr['labels']):
165+
# check if that label is one that flags a request. If at the same time there is also the corresponding accepted label, don't take this PR into account for the report.
166+
labels_take.append(label_name)
167+
168+
if not labels_take:
169+
# no labels of interest
170+
continue
166171

167-
# add all the other commits
168-
f.write('\n# Other PRs (not merged)\n')
169-
for pr in prs_other:
170-
f.write(f'| {pr["user"]["login"]} | {repo} | [PR]({pr["html_url"]}) | {pr["title"]} | not merged | {", ".join(labels_take)} | {mc_data} |\n')
172+
# if no specific MC or DATA label, assume valid for both
173+
mc_data = ','.join(mc_data) if mc_data else 'MC,DATA'
174+
merged_at = pr['merged_at'] or 'not merged'
175+
state = pr['state']
176+
for label in labels_take:
177+
rows_per_label[label].append(f'| {pr["user"]["login"]} | {repo} | [PR]({pr["html_url"]}) | {pr["title"]} | {state} | {merged_at} | {mc_data} |\n')
178+
179+
for label, rows in rows_per_label.items():
180+
if not rows:
181+
# nothing to add here
182+
continue
183+
f.write(f'\n\n### For label {label}\n\n')
184+
f.write(common_header)
185+
for row in rows:
186+
f.write(row)
171187

172188
print(f"==> Report written to {outfile}")
173189

@@ -177,11 +193,11 @@ def make_report(prs_merged, prs_other, repo, labels_request, label_accept_suffix
177193
parser = argparse.ArgumentParser(description='Retrieve closed pull requests with a specific label from a GitHub repository')
178194
parser.add_argument('--owner', help='GitHub repository owner', default='AliceO2Group')
179195
parser.add_argument('--repo', required=True, help='GitHub repository name, e.g. O2DPG or AliceO2')
180-
parser.add_argument('--pr-state', dest='pr_state', default='closed', help='The state of the PR')
181-
parser.add_argument('--output', default='o2dpg_pr_report.txt')
182-
parser.add_argument('--per-page', dest='per_page', default=50, help='How many results per page')
196+
parser.add_argument('--pr-state', dest='pr_state', help='The state of the PR')
197+
parser.add_argument('--output', help='name of the output file where the report will be written')
198+
parser.add_argument('--per-page', dest='per_page', default=100, help='How many results per page')
183199
parser.add_argument('--start-page', dest='start_page', type=int, default=1, help='Start on this page')
184-
parser.add_argument('--pages', type=int, default=1, help='Number of pages')
200+
parser.add_argument('--pages', type=int, default=5, help='Number of pages')
185201
parser.add_argument('--label-regex', dest='label_regex', help='Provide a regular expression to decide which labels to fetch.', default='^async-\w+')
186202
parser.add_argument('--label-accepted-suffix', dest='label_accepted_suffix', help='Provide a regular expression to decide which labels to fetch.', default='accept')
187203
parser.add_argument('--include-accepted', action='store_true', help='By default, only PRs are fetched where at least one label has no "<label>-accepted" label')
@@ -193,11 +209,11 @@ def make_report(prs_merged, prs_other, repo, labels_request, label_accept_suffix
193209
labels_request, _ = separate_labels_request_accept(labels, args.label_accepted_suffix)
194210

195211
# Retrieve closed pull requests with the specified label, split into merged and other (closed) PRs
196-
prs_merged, prs_other = get_prs(args.owner, args.repo, labels_request, args.pr_state, args.per_page, args.start_page, args.pages)
197-
if prs_merged is None:
198-
print('ERROR: There was a problem fetching the info.')
199-
sys.exit(1)
212+
prs = get_prs(args.owner, args.repo, labels_request, args.pr_state, args.per_page, args.start_page, args.pages)
213+
if not prs:
214+
print('==> There are no PRs to report.')
215+
sys.exit(0)
200216

201-
make_report(prs_merged, prs_other, args.repo, labels_request, args.label_accepted_suffix, args.output)
217+
make_report(prs, args.repo, labels_request, args.label_accepted_suffix, args.output)
202218

203219
sys.exit(0)

0 commit comments

Comments
 (0)