Implements evaluation and posting of change approvals on issues (#1)
* Add logic to get the comment by the script bot for a specific change * Implement comment editing logic * Update formatting for new messages
This commit is contained in:
parent
5919e03842
commit
5b055bba76
@ -52,6 +52,8 @@ def main():
|
|||||||
help='Specifies how far in the past to search for changes in Gerrit. '
|
help='Specifies how far in the past to search for changes in Gerrit. '
|
||||||
'See https://gerrit-review.googlesource.com/Documentation/user-search.html#age for more '
|
'See https://gerrit-review.googlesource.com/Documentation/user-search.html#age for more '
|
||||||
'details.')
|
'details.')
|
||||||
|
parser.add_argument('--skip-approvals', action='store_true', required=False, default=False,
|
||||||
|
help='Skips evaluation of change approvals to be written to the bot comments.')
|
||||||
parser.add_argument('-u', '--github-user', action='store', required=False, type=str,
|
parser.add_argument('-u', '--github-user', action='store', required=False, type=str,
|
||||||
default=os.getenv('GITHUB_USER', default=None),
|
default=os.getenv('GITHUB_USER', default=None),
|
||||||
help='Username to use for GitHub Issues integration. Defaults to GITHUB_USER in '
|
help='Username to use for GitHub Issues integration. Defaults to GITHUB_USER in '
|
||||||
|
@ -9,9 +9,11 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import github
|
import github
|
||||||
|
import pytz as pytz
|
||||||
from github.Repository import Repository
|
from github.Repository import Repository
|
||||||
|
|
||||||
from gerrit_to_github_issues import gerrit
|
from gerrit_to_github_issues import gerrit
|
||||||
@ -21,15 +23,15 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def update(gerrit_url: str, gerrit_project_name: str, github_project_name: str, github_user: str, github_password: str,
|
def update(gerrit_url: str, gerrit_project_name: str, github_project_name: str, github_user: str, github_password: str,
|
||||||
github_token: str, change_age: str = None):
|
github_token: str, change_age: str = None, skip_approvals: bool = False):
|
||||||
repo = github_issues.get_repo(github_project_name, github_user, github_password, github_token)
|
gh, repo = github_issues.get_repo(github_project_name, github_user, github_password, github_token)
|
||||||
change_list = gerrit.get_changes(gerrit_url, gerrit_project_name, change_age=change_age)
|
change_list = gerrit.get_changes(gerrit_url, gerrit_project_name, change_age=change_age)
|
||||||
for change in change_list['data']:
|
for change in change_list['data']:
|
||||||
if 'commitMessage' in change:
|
if 'commitMessage' in change:
|
||||||
process_change(change, repo, gerrit_url)
|
process_change(gh, change, repo, skip_approvals)
|
||||||
|
|
||||||
|
|
||||||
def process_change(change: dict, repo: Repository, gerrit_url: str):
|
def process_change(gh: github.Github, change: dict, repo: Repository, skip_approvals: bool = False):
|
||||||
issue_numbers_dict = github_issues.parse_issue_number(change['commitMessage'])
|
issue_numbers_dict = github_issues.parse_issue_number(change['commitMessage'])
|
||||||
issue_numbers_dict = github_issues.remove_duplicated_issue_numbers(issue_numbers_dict)
|
issue_numbers_dict = github_issues.remove_duplicated_issue_numbers(issue_numbers_dict)
|
||||||
if not issue_numbers_dict:
|
if not issue_numbers_dict:
|
||||||
@ -42,10 +44,8 @@ def process_change(change: dict, repo: Repository, gerrit_url: str):
|
|||||||
except github.GithubException:
|
except github.GithubException:
|
||||||
LOG.warning(f'Issue #{issue_number} not found for project')
|
LOG.warning(f'Issue #{issue_number} not found for project')
|
||||||
return
|
return
|
||||||
comment_msg = ''
|
bot_comment = github_issues.get_bot_comment(issue, gh.get_user().login, change['number'])
|
||||||
change_url = gerrit.make_gerrit_url(gerrit_url, change['number'])
|
if issue.state == 'closed' and not bot_comment:
|
||||||
link_exists = github_issues.check_issue_for_matching_comments(issue, change_url)
|
|
||||||
if issue.state == 'closed' and not link_exists:
|
|
||||||
LOG.debug(f'Issue #{issue_number} was closed, reopening...')
|
LOG.debug(f'Issue #{issue_number} was closed, reopening...')
|
||||||
issue.edit(state='open')
|
issue.edit(state='open')
|
||||||
issue.create_comment('Issue reopened due to new activity on Gerrit.\n\n')
|
issue.create_comment('Issue reopened due to new activity on Gerrit.\n\n')
|
||||||
@ -70,14 +70,54 @@ def process_change(change: dict, repo: Repository, gerrit_url: str):
|
|||||||
issue.remove_from_labels('wip')
|
issue.remove_from_labels('wip')
|
||||||
except github.GithubException:
|
except github.GithubException:
|
||||||
LOG.debug(f'`wip` tag does not exist on issue #{issue_number}')
|
LOG.debug(f'`wip` tag does not exist on issue #{issue_number}')
|
||||||
if not link_exists:
|
comment_msg = get_issue_comment(change, key, skip_approvals)
|
||||||
comment_msg += '### New Related Change\n\n' \
|
if not bot_comment:
|
||||||
f'**Link:** {change_url}\n' \
|
|
||||||
f'**Subject:** {change["subject"]}\n' \
|
|
||||||
f'**Authored By:** {change["owner"]["name"]} ({change["owner"]["email"]})'
|
|
||||||
if key == 'closes':
|
if key == 'closes':
|
||||||
comment_msg += '\n\nThis change will close this issue when merged.'
|
comment_msg += '\n\nThis change will close this issue when merged.'
|
||||||
if comment_msg:
|
|
||||||
LOG.debug(f'Comment to post on #{issue_number}: {comment_msg}')
|
LOG.debug(f'Comment to post on #{issue_number}: {comment_msg}')
|
||||||
issue.create_comment(comment_msg)
|
issue.create_comment(comment_msg)
|
||||||
LOG.info(f'Comment posted to issue #{issue_number}')
|
LOG.info(f'Comment posted to issue #{issue_number}')
|
||||||
|
else:
|
||||||
|
LOG.debug(f'Comment to edit on #{issue_number}: {comment_msg}')
|
||||||
|
comment = github_issues.get_bot_comment(issue, gh.get_user().login, change['number'])
|
||||||
|
comment.edit(comment_msg)
|
||||||
|
LOG.info(f'Comment edited to issue #{issue_number}')
|
||||||
|
|
||||||
|
|
||||||
|
def get_issue_comment(change: dict, key: str, skip_approvals: bool = False) -> str:
|
||||||
|
comment_str = f'## Related Change [#{change["number"]}]({change["url"]})\n\n' \
|
||||||
|
f'**Subject:** {change["subject"]}\n' \
|
||||||
|
f'**Link:** {change["url"]}\n' \
|
||||||
|
f'**Status:** {change["status"]}\n' \
|
||||||
|
f'**Owner:** {change["owner"]["name"]} ({change["owner"]["email"]})\n\n'
|
||||||
|
if key == 'closes':
|
||||||
|
comment_str += 'This change will close this issue when merged.\n\n'
|
||||||
|
if not skip_approvals:
|
||||||
|
comment_str += '### Approvals\n' \
|
||||||
|
'```diff\n'
|
||||||
|
|
||||||
|
approval_dict = {
|
||||||
|
'Code-Review': [],
|
||||||
|
'Verified': [],
|
||||||
|
'Workflow': []
|
||||||
|
}
|
||||||
|
if 'approvals' in change['currentPatchSet']:
|
||||||
|
for approval in change['currentPatchSet']['approvals']:
|
||||||
|
if approval['type'] in approval_dict:
|
||||||
|
approval_dict[approval['type']].append((approval['by']['name'], approval['value']))
|
||||||
|
else:
|
||||||
|
LOG.warning(f'Approval type "{approval["type"]}" is not a known approval type')
|
||||||
|
|
||||||
|
for key in ['Code-Review', 'Verified', 'Workflow']:
|
||||||
|
comment_str += f'{key}\n'
|
||||||
|
if approval_dict[key]:
|
||||||
|
for approval in approval_dict[key]:
|
||||||
|
if int(approval[1]) > 0:
|
||||||
|
comment_str += '+'
|
||||||
|
comment_str += f'{approval[1]} {approval[0]}\n'
|
||||||
|
else:
|
||||||
|
comment_str += '! None\n'
|
||||||
|
comment_str += '```'
|
||||||
|
dt = datetime.datetime.now(pytz.timezone('America/Chicago')).strftime('%Y-%m-%d %H:%M:%S %Z').strip()
|
||||||
|
comment_str += f'\n\n*Last Updated: {dt}*'
|
||||||
|
return comment_str
|
||||||
|
@ -15,7 +15,7 @@ from fabric import Connection
|
|||||||
|
|
||||||
|
|
||||||
def get_changes(gerrit_url: str, project_name: str, port: int = 29418, change_age: str = None) -> dict:
|
def get_changes(gerrit_url: str, project_name: str, port: int = 29418, change_age: str = None) -> dict:
|
||||||
cmd = f'gerrit query --format=JSON status:open project:{project_name}'
|
cmd = f'gerrit query --format=JSON --current-patch-set project:{project_name}'
|
||||||
if change_age:
|
if change_age:
|
||||||
cmd += f' -- -age:{change_age}'
|
cmd += f' -- -age:{change_age}'
|
||||||
result = Connection(gerrit_url, port=port).run(cmd)
|
result = Connection(gerrit_url, port=port).run(cmd)
|
||||||
|
@ -14,6 +14,7 @@ import re
|
|||||||
|
|
||||||
import github
|
import github
|
||||||
from github.Issue import Issue
|
from github.Issue import Issue
|
||||||
|
from github.IssueComment import IssueComment
|
||||||
from github.Repository import Repository
|
from github.Repository import Repository
|
||||||
|
|
||||||
from gerrit_to_github_issues import errors
|
from gerrit_to_github_issues import errors
|
||||||
@ -60,18 +61,17 @@ def remove_duplicated_issue_numbers(issue_dict: dict) -> dict:
|
|||||||
return issue_dict
|
return issue_dict
|
||||||
|
|
||||||
|
|
||||||
def get_repo(repo_name: str, github_user: str, github_pw: str, github_token: str) -> Repository:
|
def get_repo(repo_name: str, github_user: str, github_pw: str, github_token: str) -> (github.Github, Repository):
|
||||||
if github_token:
|
if github_token:
|
||||||
gh = github.Github(github_token)
|
gh = github.Github(github_token)
|
||||||
elif github_user and github_pw:
|
elif github_user and github_pw:
|
||||||
gh = github.Github(github_user, github_pw)
|
gh = github.Github(github_user, github_pw)
|
||||||
else:
|
else:
|
||||||
raise errors.GithubConfigurationError
|
raise errors.GithubConfigurationError
|
||||||
return gh.get_repo(repo_name)
|
return gh, gh.get_repo(repo_name)
|
||||||
|
|
||||||
|
|
||||||
def check_issue_for_matching_comments(issue: Issue, contains: str) -> bool:
|
def get_bot_comment(issue: Issue, bot_name: str, ps_number: str) -> IssueComment:
|
||||||
for comment in issue.get_comments():
|
for i in issue.get_comments():
|
||||||
if contains in comment.body:
|
if i.user.login == bot_name and ps_number in i.body:
|
||||||
return True
|
return i
|
||||||
return False
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user