Skip to content

Commit f70cbe6

Browse files
committed
add script to track open issues and pull requests
1 parent 1f0f582 commit f70cbe6

File tree

1 file changed

+163
-0
lines changed

1 file changed

+163
-0
lines changed

track_issues_and_pull_requests.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Ensure all open issues are tracked in the Backlog project in the Pending Review
4+
column and all open pull requests are tracked in the Active Sprint project in
5+
the Code Review column.
6+
"""
7+
8+
# Standard library
9+
import argparse
10+
import sys
11+
import traceback
12+
13+
# First-party/Local
14+
import ccos.log
15+
from ccos import gh_utils
16+
17+
ISSUES_COLUMN = "Pending Review"
18+
ISSUES_PROJECT = "Backlog"
19+
LOG = ccos.log.setup_logger()
20+
PULL_REQUESTS_COLUMN = "Code Review"
21+
PULL_REQUESTS_PROJECT = "Active Sprint"
22+
23+
24+
class ScriptError(Exception):
25+
def __init__(self, message, code=None):
26+
self.code = code if code else 1
27+
message = "({}) {}".format(self.code, message)
28+
super(ScriptError, self).__init__(message)
29+
30+
31+
def setup():
32+
"""Instantiate and configure argparse and logging.
33+
34+
Return argsparse namespace.
35+
"""
36+
ap = argparse.ArgumentParser(description=__doc__)
37+
ap.add_argument(
38+
"-n",
39+
"--dryrun",
40+
action="store_true",
41+
help="dry run: do not make any changes",
42+
)
43+
args = ap.parse_args()
44+
return args
45+
46+
47+
def get_untracked_issues(github_client):
48+
LOG.info("Searching for untracked open issues")
49+
# https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
50+
query = "org:creativecommons state:open no:project type:issue"
51+
LOG.debug(f"issues query: {query}")
52+
untracked_issues = list(github_client.search_issues(query=query))
53+
untracked_issues.sort(key=lambda x: f"{x.repository.name}{x.number:09}")
54+
return untracked_issues
55+
56+
57+
def track_issues(args, gh_org_cc, untracked_issues):
58+
if not untracked_issues:
59+
LOG.info(
60+
f"No untracked issues to add to {ISSUES_PROJECT}: {ISSUES_COLUMN}"
61+
)
62+
return
63+
LOG.info(
64+
f"Adding {len(untracked_issues)} issues to {ISSUES_PROJECT}:"
65+
f" {ISSUES_COLUMN}"
66+
)
67+
for project in gh_org_cc.get_projects():
68+
if project.name != ISSUES_PROJECT:
69+
continue
70+
for column in project.get_columns():
71+
if column.name != ISSUES_COLUMN:
72+
continue
73+
for issue in untracked_issues:
74+
if not args.dryrun:
75+
no_op = ""
76+
column.create_card(
77+
content_id=issue.id,
78+
content_type="Issue",
79+
)
80+
else:
81+
no_op = "(no-op) "
82+
LOG.change_indent(+1)
83+
LOG.success(
84+
f"{no_op}{issue.repository.name}#{issue.number}"
85+
f" {issue.title}"
86+
)
87+
LOG.change_indent(-1)
88+
89+
90+
def get_untracked_pull_requests(github_client):
91+
LOG.info("Searching for untracked open pull requests")
92+
# https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
93+
query = "org:creativecommons state:open no:project type:pr"
94+
LOG.debug(f"pull request query: {query}")
95+
untracked_pull_requests = list(github_client.search_issues(query=query))
96+
untracked_pull_requests.sort(
97+
key=lambda x: f"{x.repository.name}{x.number:09}"
98+
)
99+
return untracked_pull_requests
100+
101+
102+
def track_pull_requests(args, gh_org_cc, untracked_pull_requests):
103+
if not untracked_pull_requests:
104+
LOG.info(
105+
f"No untracked pull requests to add to {PULL_REQUESTS_PROJECT}:"
106+
f" {PULL_REQUESTS_COLUMN}"
107+
)
108+
return
109+
LOG.info(
110+
f"Adding {len(untracked_pull_requests)} pull requests to"
111+
f" {PULL_REQUESTS_PROJECT}: {PULL_REQUESTS_COLUMN}"
112+
)
113+
for project in gh_org_cc.get_projects():
114+
if project.name != PULL_REQUESTS_PROJECT:
115+
continue
116+
for column in project.get_columns():
117+
if column.name != PULL_REQUESTS_COLUMN:
118+
continue
119+
for pull_request in untracked_pull_requests:
120+
if not args.dryrun:
121+
no_op = ""
122+
column.create_card(
123+
content_id=pull_request.id,
124+
# Based on the code samples I found elsewhere, I
125+
# expect this to be "PullRequest", but that doesn't
126+
# work and "Issue" does ¯\_(ツ)_/¯
127+
content_type="Issue",
128+
)
129+
else:
130+
no_op = "(no-op) "
131+
LOG.change_indent(+1)
132+
LOG.success(
133+
f"{no_op}{pull_request.repository.name}"
134+
f"#{pull_request.number} {pull_request.title}"
135+
)
136+
LOG.change_indent(-1)
137+
138+
139+
def main():
140+
args = setup()
141+
github_client = gh_utils.set_up_github_client()
142+
gh_org_cc = gh_utils.get_cc_organization(github_client)
143+
untracked_issues = get_untracked_issues(github_client)
144+
track_issues(args, gh_org_cc, untracked_issues)
145+
untracked_pull_requests = get_untracked_pull_requests(github_client)
146+
track_pull_requests(args, gh_org_cc, untracked_pull_requests)
147+
148+
149+
if __name__ == "__main__":
150+
try:
151+
main()
152+
except SystemExit as e:
153+
sys.exit(e.code)
154+
except KeyboardInterrupt:
155+
LOG.info("Halted via KeyboardInterrupt.")
156+
sys.exit(130)
157+
except ScriptError:
158+
error_type, error_value, error_traceback = sys.exc_info()
159+
LOG.critical(f"{error_value}")
160+
sys.exit(error_value.code)
161+
except Exception:
162+
LOG.error(f"Unhandled exception: {traceback.format_exc()}")
163+
sys.exit(1)

0 commit comments

Comments
 (0)