Skip to content

Commit 0aee2a3

Browse files
benedikt-voelkelBenedikt Volkel
andauthored
Get all labels and change table organisation (#1537)
Logic is now the following: 1. Fetch all labels (optionally given some regex). 2. Get closed PRs that have at least one of those labels. 3. Split into merged and not merged. 4. Populate a table per label and list merged PRs. (A PR can therefore appear in more than one table.) 5. Write everything to a txt file. Co-authored-by: Benedikt Volkel <benedikt.volkel@cern.ch>
1 parent 1116949 commit 0aee2a3

File tree

1 file changed

+90
-28
lines changed

1 file changed

+90
-28
lines changed

UTILS/o2dpg_make_github_pr_report.py

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import argparse
88
import requests
9+
import re
910

1011

1112
def organise_prs(prs):
@@ -29,23 +30,21 @@ def organise_prs(prs):
2930
merged_at.append(pr['merged_at'])
3031

3132
# sort the merged PRs by their merged timestamp
32-
prs_merged = [pr for _, pr in sorted(zip(merged_at, prs))]
33+
prs_merged = [pr for _, pr in sorted(zip(merged_at, prs_merged))]
3334

3435
return prs_merged, prs_other
3536

3637

37-
def get_prs(owner, repo, prod_label, pr_state, include_unmerged, per_page=50, start_page=1, pages=1):
38+
def get_prs(owner, repo, request_labels, pr_state, per_page=50, start_page=1, pages=1):
3839
"""
3940
Get PRs according to some selection
4041
"""
4142
# GitHub API endpoint for listing closed pull requests with a specific label
42-
merged_token = '&is:merged=true' if not include_unmerged else ''
4343
prs_return = []
4444

4545
has_error = False
4646
for page in range(start_page, pages + 1):
47-
url = f'https://api.github.com/repos/{owner}/{repo}/pulls?state={pr_state}{merged_token}&page={page}&per_page={per_page}'
48-
print(f'Fetch PRs accrodring to {url}')
47+
url = f'https://api.github.com/repos/{owner}/{repo}/pulls?state={pr_state}&page={page}&per_page={per_page}'
4948

5049
# Send GET request to GitHub API
5150
response = requests.get(url)
@@ -57,13 +56,13 @@ def get_prs(owner, repo, prod_label, pr_state, include_unmerged, per_page=50, st
5756
# PRs to return because we filter on a specific label
5857
for pr in prs:
5958
labels = pr['labels']
60-
accept = False
59+
take_pr = False
6160
for label in labels:
62-
if label['name'] == prod_label:
61+
if label['name'] in request_labels:
6362
# only with the correct the label will be accepted
64-
accept = True
63+
take_pr = True
6564
break
66-
if not accept:
65+
if not take_pr:
6766
continue
6867
# we will end up here if accepted, so append
6968
prs_return.append(pr)
@@ -80,62 +79,125 @@ def get_prs(owner, repo, prod_label, pr_state, include_unmerged, per_page=50, st
8079
return organise_prs(prs_return)
8180

8281

83-
def make_report(prs_merged, prs_other, outfile):
82+
def get_labels(owner, repo, regex=None):
83+
"""
84+
Get the labels that match given regex
85+
"""
86+
# the REST API url
87+
url = f'https://api.github.com/repos/{owner}/{repo}/labels'
88+
# Send GET request to GitHub API
89+
response = requests.get(url)
90+
91+
if response.status_code != 200:
92+
print(f'ERROR: Problem to retrieve labels for owner {owner} and repository {repo}')
93+
return None
94+
95+
return [label['name'] for label in response.json() if not repo or re.match(regex, label['name'])]
96+
97+
98+
def separate_labels_request_accept(labels, accept_suffix=None):
99+
"""
100+
Disentangle labels and <label>-accepted
101+
"""
102+
if not accept_suffix:
103+
# all labels are considered request labels
104+
return labels.copy(), []
105+
106+
labels_request = []
107+
labels_accept = []
108+
109+
for label in labels:
110+
if label.endswith(accept_suffix):
111+
labels_accept.append(label)
112+
continue
113+
labels_request.append(label)
114+
115+
return labels_request, labels_accept
116+
117+
118+
def make_report(prs_merged, prs_other, repo, labels_request, label_accept_suffix, outfile):
84119
"""
85120
Make a report
86121
87-
simply dump into text file
122+
The report consists of one table per label which will be written to a text file.
88123
"""
124+
# 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}
89127

90128
with open(outfile, 'w') as f:
91-
f.write('# FROM OLDEST TO RECENT\n')
92-
# our common header
93-
f.write('| Date of next tag | Requestor | Package | PR | Data or MC | Comment | JIRA (if it exists) | Accepted | In production | Validated by requestor |\n')
94-
f.write('| ---------------- | ------------ | ------- | --------------------------------------------------------:|:--------------------------------------------- | ------------------- | ---------------- | ------------- |-------------| ------------------|\n')
129+
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')
95132

96133
# first put the merged PRs
97134
for pr in prs_merged:
98135
mc_data = []
136+
# collect the labels for which table this PR should be taken into account
137+
labels_take = []
99138

100139
for label in pr['labels']:
101-
if label['name'] in ('MC', 'DATA'):
140+
label_name = label['name']
141+
if label_name.lower() in ('mc', 'data'):
102142
# get assigned MC or DATA label if this PR has it
103143
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
104151

105152
# if no specific MC or DATA label, assume valid for both
106153
mc_data = ','.join(mc_data) if mc_data else 'MC,DATA'
107-
# add the full line to the output file
108-
f.write(f'| {args.date} | {pr["user"]["login"]} | {args.repo} | [PR]({pr["html_url"]}) | {mc_data} | {pr["title"]} | | | | |\n')
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')
156+
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')
109166

110167
# add all the other commits
111-
f.write('OTHER PRs\n')
168+
f.write('\n# Other PRs (not merged)\n')
112169
for pr in prs_other:
113-
f.write(f'| {args.date} | {pr["user"]["login"]} | {args.repo} | [PR]({pr["html_url"]}) | | {pr["title"]} | | | | |\n')
170+
f.write(f'| {pr["user"]["login"]} | {repo} | [PR]({pr["html_url"]}) | {pr["title"]} | not merged | {", ".join(labels_take)} | {mc_data} |\n')
171+
172+
print(f"==> Report written to {outfile}")
114173

115174

116175
if __name__ == '__main__':
117176
# Parse command-line arguments
118177
parser = argparse.ArgumentParser(description='Retrieve closed pull requests with a specific label from a GitHub repository')
119178
parser.add_argument('--owner', help='GitHub repository owner', default='AliceO2Group')
120179
parser.add_argument('--repo', required=True, help='GitHub repository name, e.g. O2DPG or AliceO2')
121-
parser.add_argument('--prod-label', dest='prod_label', required=True, help='Production label to filter PRs')
122180
parser.add_argument('--pr-state', dest='pr_state', default='closed', help='The state of the PR')
123-
parser.add_argument('--include-unmerged', dest='include_unmerged', action='store_true', help='To fetch also unmerged PRs')
124181
parser.add_argument('--output', default='o2dpg_pr_report.txt')
125-
parser.add_argument('--date', help='The date tag to be put', required=True)
126182
parser.add_argument('--per-page', dest='per_page', default=50, help='How many results per page')
127183
parser.add_argument('--start-page', dest='start_page', type=int, default=1, help='Start on this page')
128184
parser.add_argument('--pages', type=int, default=1, help='Number of pages')
129-
130-
185+
parser.add_argument('--label-regex', dest='label_regex', help='Provide a regular expression to decide which labels to fetch.', default='^async-\w+')
186+
parser.add_argument('--label-accepted-suffix', dest='label_accepted_suffix', help='Provide a regular expression to decide which labels to fetch.', default='accept')
187+
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')
131188
args = parser.parse_args()
132189

133-
# Retrieve closed pull requests with the specified label
134-
prs_merged, prs_other = get_prs(args.owner, args.repo, args.prod_label, args.pr_state, args.include_unmerged, args.per_page, args.start_page, args.pages)
190+
# get all labels of interest
191+
labels = get_labels(args.owner, args.repo, args.label_regex)
192+
# split into request and accept labels, here we currently only need the request labels
193+
labels_request, _ = separate_labels_request_accept(labels, args.label_accepted_suffix)
194+
195+
# 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)
135197
if prs_merged is None:
136198
print('ERROR: There was a problem fetching the info.')
137199
sys.exit(1)
138200

139-
make_report(prs_merged, prs_other, args.output)
201+
make_report(prs_merged, prs_other, args.repo, labels_request, args.label_accepted_suffix, args.output)
140202

141203
sys.exit(0)

0 commit comments

Comments
 (0)