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.
This commit is contained in:
parent
09fa974779
commit
727e8eb44d
1
AUTHORS
1
AUTHORS
@ -1,2 +1,3 @@
|
|||||||
Ian H Pittwood <pittwoodian@gmail.com>
|
Ian H Pittwood <pittwoodian@gmail.com>
|
||||||
Ian Pittwood <pittwoodian@gmail.com>
|
Ian Pittwood <pittwoodian@gmail.com>
|
||||||
|
Ian Howell <ian.howell0@gmail.com>
|
||||||
|
@ -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'
|
'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 '
|
'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'
|
'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 '
|
'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 such as "ready for review".\n'
|
'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" '
|
'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
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
)
|
)
|
||||||
parser.add_argument('-g', '--gerrit-url', action='store', required=True, type=str,
|
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.')
|
default=False, help='Enabled DEBUG level logging.')
|
||||||
parser.add_argument('--log-file', action='store', required=False, type=str,
|
parser.add_argument('--log-file', action='store', required=False, type=str,
|
||||||
help='Specifies a file to output logs to. Defaults to `sys.stdout`.')
|
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('gerrit_repo_name', action='store', type=str, help='Target Gerrit repo.')
|
||||||
parser.add_argument('github_project_name', action='store', type=str, help='Target Github project.')
|
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()
|
ns = parser.parse_args()
|
||||||
args = validate(ns)
|
args = validate(ns)
|
||||||
verbose = args.pop('verbose')
|
verbose = args.pop('verbose')
|
||||||
|
@ -15,6 +15,8 @@ import logging
|
|||||||
import github
|
import github
|
||||||
import pytz as pytz
|
import pytz as pytz
|
||||||
from github.Repository import Repository
|
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 gerrit
|
||||||
from gerrit_to_github_issues import github_issues
|
from gerrit_to_github_issues import github_issues
|
||||||
@ -22,16 +24,19 @@ from gerrit_to_github_issues import github_issues
|
|||||||
LOG = logging.getLogger(__name__)
|
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_id: int,
|
||||||
github_token: str, change_age: str = None, skip_approvals: bool = False):
|
github_repo_name: str, github_user: str, github_password: str, github_token: str,
|
||||||
gh, repo = github_issues.get_repo(github_project_name, github_user, github_password, github_token)
|
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)
|
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(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.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:
|
||||||
@ -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'])
|
bot_comment = github_issues.get_bot_comment(issue, gh.get_user().login, change['number'])
|
||||||
if issue.state == 'closed' and not bot_comment:
|
if issue.state == 'closed' and not bot_comment:
|
||||||
LOG.debug(f'Issue #{issue_number} was closed, reopening...')
|
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.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')
|
||||||
|
|
||||||
labels = [str(l.name) for l in list(issue.get_labels())]
|
labels = [str(l.name) for l in list(issue.get_labels())]
|
||||||
if 'WIP' in change['commitMessage'] or 'DNM' in change['commitMessage']:
|
if 'WIP' in change['commitMessage'] or 'DNM' in change['commitMessage']:
|
||||||
if 'wip' not in labels:
|
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')
|
issue.remove_from_labels('ready for review')
|
||||||
except github.GithubException:
|
except github.GithubException:
|
||||||
LOG.debug(f'`ready for review` tag does not exist on issue #{issue_number}')
|
LOG.debug(f'`ready for review` tag does not exist on issue #{issue_number}')
|
||||||
|
move_issue(project_board, issue, 'In Progress')
|
||||||
else:
|
else:
|
||||||
if 'ready for review' not in labels:
|
if 'ready for review' not in labels:
|
||||||
LOG.debug(f'add `ready for review` to #{issue_number}')
|
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')
|
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}')
|
||||||
|
move_issue(project_board, issue, 'Submitted on Gerrit')
|
||||||
comment_msg = get_issue_comment(change, key, skip_approvals)
|
comment_msg = get_issue_comment(change, key, skip_approvals)
|
||||||
if not bot_comment:
|
if not bot_comment:
|
||||||
if key == 'closes':
|
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()
|
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}*'
|
comment_str += f'\n\n*Last Updated: {dt}*'
|
||||||
return comment_str
|
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}"')
|
||||||
|
@ -61,14 +61,13 @@ 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) -> (github.Github, Repository):
|
def get_client(github_user: str, github_pw: str, github_token: str) -> github.Github
|
||||||
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, gh.get_repo(repo_name)
|
|
||||||
|
|
||||||
|
|
||||||
def get_bot_comment(issue: Issue, bot_name: str, ps_number: str) -> IssueComment:
|
def get_bot_comment(issue: Issue, bot_name: str, ps_number: str) -> IssueComment:
|
||||||
|
Loading…
Reference in New Issue
Block a user