From 727e8eb44d87c826fa927a7a8f28398cea225ab0 Mon Sep 17 00:00:00 2001 From: Ian Howell Date: Thu, 30 Apr 2020 14:24:26 -0500 Subject: [PATCH] Integrate with the github project board Implement a feature allowing the bot to move issues around on the project board: * When a change's commit message includes "WIP" or "DNM", the issue will be moved to the "In Progress" column. * When a change's commit message does *not* include "WIP" or "DNM", assume that the change is ready for review, and move it to the "Submitted on Gerrit" column. --- AUTHORS | 1 + gerrit_to_github_issues/cli.py | 13 ++++--- gerrit_to_github_issues/engine.py | 45 +++++++++++++++++++++--- gerrit_to_github_issues/github_issues.py | 3 +- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/AUTHORS b/AUTHORS index c4faf9b..cd30bd6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Ian H Pittwood Ian Pittwood +Ian Howell diff --git a/gerrit_to_github_issues/cli.py b/gerrit_to_github_issues/cli.py index ce9d53d..8a22602 100644 --- a/gerrit_to_github_issues/cli.py +++ b/gerrit_to_github_issues/cli.py @@ -39,10 +39,12 @@ def main(): '2. Check associated Github Issue for a link to the change. If no such link exists, comment it.\n' '3. If the associated issue was closed, re-open it and comment on it describing why it was ' 're-opened and a link to the Gerrit change that was found.\n' - '4. If the Gerrit change\'s commit message contains a "WIP" or "DNM" tag, add the "wip" label and ' - 'to the issue remove other process labels such as "ready for review".\n' + '4. If the Gerrit change\'s commit message contains a "WIP" or "DNM" tag, add the "wip" label ' + 'to the issue, remove other process labels (e.g. "ready for review"), and move the issue ' + 'to the "In Progress" column of the project board.\n' '5. If no "WIP" or "DNM" tag is found in the change\'s commit message, add the "ready for review" ' - 'label to the issue and remove other process labels such as "ready for review".', + 'label to the issue, remove other process labels (e.g "wip"), and move the issue ' + 'to the "Submitted on Gerrit" column of the project board.', formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument('-g', '--gerrit-url', action='store', required=True, type=str, @@ -70,8 +72,9 @@ def main(): default=False, help='Enabled DEBUG level logging.') parser.add_argument('--log-file', action='store', required=False, type=str, help='Specifies a file to output logs to. Defaults to `sys.stdout`.') - parser.add_argument('gerrit_project_name', action='store', type=str, help='Target Gerrit project.') - parser.add_argument('github_project_name', action='store', type=str, help='Target Github project.') + parser.add_argument('gerrit_repo_name', action='store', type=str, help='Target Gerrit repo.') + parser.add_argument('github_repo_name', action='store', type=str, help='Target Github repo.') + parser.add_argument('github_project_id', action='store', type=int, help='Target Github project board ID.') ns = parser.parse_args() args = validate(ns) verbose = args.pop('verbose') diff --git a/gerrit_to_github_issues/engine.py b/gerrit_to_github_issues/engine.py index 6ad6012..cc75dbb 100644 --- a/gerrit_to_github_issues/engine.py +++ b/gerrit_to_github_issues/engine.py @@ -15,6 +15,8 @@ import logging import github import pytz as pytz from github.Repository import Repository +from github.Project import Project +from github.Issue import Issue from gerrit_to_github_issues import gerrit from gerrit_to_github_issues import github_issues @@ -22,16 +24,19 @@ from gerrit_to_github_issues import github_issues LOG = logging.getLogger(__name__) -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, skip_approvals: bool = False): - gh, repo = github_issues.get_repo(github_project_name, github_user, github_password, github_token) +def update(gerrit_url: str, gerrit_project_name: str, github_project_id: int, + github_repo_name: str, github_user: str, github_password: str, github_token: str, + change_age: str = None, skip_approvals: bool = False): + gh = github_issues.get_client(github_user, github_password, github_token) + repo = gh.get_repo(github_repo_name) + project_board = gh.get_project(github_project_id) change_list = gerrit.get_changes(gerrit_url, gerrit_project_name, change_age=change_age) for change in change_list['data']: if 'commitMessage' in change: - process_change(gh, change, repo, skip_approvals) + process_change(gh, change, repo, project_board, skip_approvals) -def process_change(gh: github.Github, change: dict, repo: Repository, skip_approvals: bool = False): +def process_change(gh: github.Github, change: dict, repo: Repository, project_board: Project, skip_approvals: bool = False): issue_numbers_dict = github_issues.parse_issue_number(change['commitMessage']) issue_numbers_dict = github_issues.remove_duplicated_issue_numbers(issue_numbers_dict) if not issue_numbers_dict: @@ -47,8 +52,13 @@ def process_change(gh: github.Github, change: dict, repo: Repository, skip_appro bot_comment = github_issues.get_bot_comment(issue, gh.get_user().login, change['number']) if issue.state == 'closed' and not bot_comment: LOG.debug(f'Issue #{issue_number} was closed, reopening...') + + # NOTE(howell): Reopening a closed issue will move it from the + # "Done" column to the "In Progress" column on the project + # board via Github automation. issue.edit(state='open') issue.create_comment('Issue reopened due to new activity on Gerrit.\n\n') + labels = [str(l.name) for l in list(issue.get_labels())] if 'WIP' in change['commitMessage'] or 'DNM' in change['commitMessage']: if 'wip' not in labels: @@ -60,6 +70,7 @@ def process_change(gh: github.Github, change: dict, repo: Repository, skip_appro issue.remove_from_labels('ready for review') except github.GithubException: LOG.debug(f'`ready for review` tag does not exist on issue #{issue_number}') + move_issue(project_board, issue, 'In Progress') else: if 'ready for review' not in labels: LOG.debug(f'add `ready for review` to #{issue_number}') @@ -70,6 +81,7 @@ def process_change(gh: github.Github, change: dict, repo: Repository, skip_appro issue.remove_from_labels('wip') except github.GithubException: LOG.debug(f'`wip` tag does not exist on issue #{issue_number}') + move_issue(project_board, issue, 'Submitted on Gerrit') comment_msg = get_issue_comment(change, key, skip_approvals) if not bot_comment: if key == 'closes': @@ -121,3 +133,26 @@ def get_issue_comment(change: dict, key: str, skip_approvals: bool = False) -> s 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 + + +def move_issue(project_board, issue, to_col_name): + for col in project_board.get_columns(): + if col.name == to_col_name: + to_col = col + else: + for c in col.get_cards(): + if c.get_content() == issue: + card = c + + if not to_col: + LOG.warning(f'Column with name "{to_col_name}" could not be found for project "{project_board.name}"') + return + + if not card: + LOG.warning(f'Issue with name "{issue.name}" could not be found for project "{project_board.name}"') + return + + if card.move("top", to_col): + LOG.info('Moved issue "{issue.name}" to column "{to_col_name}"') + else: + LOG.warning('Failed to move issue "{issue.name}" to column "{to_col_name}"') diff --git a/gerrit_to_github_issues/github_issues.py b/gerrit_to_github_issues/github_issues.py index 3a9b1f7..a40f639 100644 --- a/gerrit_to_github_issues/github_issues.py +++ b/gerrit_to_github_issues/github_issues.py @@ -61,14 +61,13 @@ def remove_duplicated_issue_numbers(issue_dict: dict) -> dict: return issue_dict -def get_repo(repo_name: str, github_user: str, github_pw: str, github_token: str) -> (github.Github, Repository): +def get_client(github_user: str, github_pw: str, github_token: str) -> github.Github if github_token: gh = github.Github(github_token) elif github_user and github_pw: gh = github.Github(github_user, github_pw) else: raise errors.GithubConfigurationError - return gh, gh.get_repo(repo_name) def get_bot_comment(issue: Issue, bot_name: str, ps_number: str) -> IssueComment: