xref: /Lucene/dev-tools/scripts/githubPRs.py (revision 674b66dd16f5b89b7c97ea9aedcac077e6d7f3b9)
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Licensed to the Apache Software Foundation (ASF) under one or more
4# contributor license agreements.  See the NOTICE file distributed with
5# this work for additional information regarding copyright ownership.
6# The ASF licenses this file to You under the Apache License, Version 2.0
7# (the "License"); you may not use this file except in compliance with
8# the License.  You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18"""
19Simple script that queries GitHub for all open PRs, then finds the ones without
20issue number in title, and the ones where the linked JIRA is already closed
21"""
22
23import os
24import sys
25sys.path.append(os.path.dirname(__file__))
26import argparse
27import json
28import re
29from github import Github
30from jira import JIRA
31from datetime import datetime
32from time import strftime
33try:
34  from jinja2 import Environment, BaseLoader
35  can_do_html = True
36except:
37  can_do_html = False
38
39def read_config():
40  parser = argparse.ArgumentParser(description='Find open Pull Requests that need attention')
41  parser.add_argument('--json', action='store_true', default=False, help='Output as json')
42  parser.add_argument('--html', action='store_true', default=False, help='Output as html')
43  parser.add_argument('--token', help='Github access token in case you query too often anonymously')
44  newconf = parser.parse_args()
45  return newconf
46
47
48def out(text):
49  global conf
50  if not (conf.json or conf.html):
51    print(text)
52
53def make_html(dict):
54  if not can_do_html:
55    print ("ERROR: Cannot generate HTML. Please install jinja2")
56    sys.exit(1)
57  global conf
58  template = Environment(loader=BaseLoader).from_string("""
59  <h1>Lucene Github PR report</h1>
60
61  <p>Number of open Pull Requests: {{ open_count }}</p>
62
63  <h2>PRs lacking JIRA reference in title ({{ no_jira_count }})</h2>
64  <ul>
65  {% for pr in no_jira %}
66    <li><a href="https://github.com/apache/lucene/pull/{{ pr.number }}">#{{ pr.number }}: {{ pr.created }} {{ pr.title }}</a> ({{ pr.user }})</li>
67  {%- endfor %}
68  </ul>
69
70  <h2>Open PRs with a resolved JIRA ({{ closed_jira_count }})</h2>
71  <ul>
72  {% for pr in closed_jira %}
73    <li><a href="https://github.com/apache/lucene/pull/{{ pr.pr_number }}">#{{ pr.pr_number }}</a>: <a href="https://issues.apache.org/jira/browse/{{ pr.issue_key }}">{{ pr.status }} {{ pr.resolution_date }} {{ pr.issue_key}}: {{ pr.issue_summary }}</a> ({{ pr.assignee }})</li>
74  {%- endfor %}
75  </ul>
76  """)
77  return template.render(dict)
78
79def main():
80  global conf
81  conf = read_config()
82  token = conf.token if conf.token is not None else None
83  if token:
84    gh = Github(token)
85  else:
86    gh = Github()
87  jira = JIRA('https://issues.apache.org/jira')
88  result = {}
89  repo = gh.get_repo('apache/lucene')
90  open_prs = repo.get_pulls(state='open')
91  out("Lucene Github PR report")
92  out("============================")
93  out("Number of open Pull Requests: %s" % open_prs.totalCount)
94  result['open_count'] = open_prs.totalCount
95
96  lack_jira = list(filter(lambda x: not re.match(r'.*\b(LUCENE)-\d{3,6}\b', x.title), open_prs))
97  result['no_jira_count'] = len(lack_jira)
98  lack_jira_list = []
99  for pr in lack_jira:
100    lack_jira_list.append({'title': pr.title, 'number': pr.number, 'user': pr.user.login, 'created': pr.created_at.strftime("%Y-%m-%d")})
101  result['no_jira'] = lack_jira_list
102  out("\nPRs lacking JIRA reference in title")
103  for pr in lack_jira_list:
104    out("  #%s: %s %s (%s)" % (pr['number'], pr['created'], pr['title'], pr['user'] ))
105
106  out("\nOpen PRs with a resolved JIRA")
107  has_jira = list(filter(lambda x: re.match(r'.*\b(LUCENE)-\d{3,6}\b', x.title), open_prs))
108
109  issue_ids = []
110  issue_to_pr = {}
111  for pr in has_jira:
112    jira_issue_str = re.match(r'.*\b((LUCENE)-\d{3,6})\b', pr.title).group(1)
113    issue_ids.append(jira_issue_str)
114    issue_to_pr[jira_issue_str] = pr
115
116  resolved_jiras = jira.search_issues(jql_str="key in (%s) AND status in ('Closed', 'Resolved')" % ", ".join(issue_ids))
117  closed_jiras = []
118  for issue in resolved_jiras:
119    pr_title = issue_to_pr[issue.key].title
120    pr_number = issue_to_pr[issue.key].number
121    assignee = issue.fields.assignee.name if issue.fields.assignee else None
122    closed_jiras.append({ 'issue_key': issue.key,
123                           'status': issue.fields.status.name,
124                           'resolution': issue.fields.resolution.name,
125                           'resolution_date': issue.fields.resolutiondate[:10],
126                           'pr_number': pr_number,
127                           'pr_title': pr_title,
128                           'issue_summary': issue.fields.summary,
129                           'assignee': assignee})
130
131  closed_jiras.sort(key=lambda r: r['pr_number'], reverse=True)
132  for issue in closed_jiras:
133    out("  #%s: %s %s %s: %s (%s)" % (issue['pr_number'],
134                                  issue['status'],
135                                  issue['resolution_date'],
136                                  issue['issue_key'],
137                                  issue['issue_summary'],
138                                  issue['assignee'])
139        )
140  result['closed_jira_count'] = len(resolved_jiras)
141  result['closed_jira'] = closed_jiras
142
143  if conf.json:
144    print(json.dumps(result, indent=4))
145
146  if conf.html:
147    print(make_html(result))
148
149if __name__ == '__main__':
150  try:
151    main()
152  except KeyboardInterrupt:
153    print('\nReceived Ctrl-C, exiting early')
154