def jira_issue_rejected(issue): issue_key = to_unicode(issue["key"]) pr_num = github_pr_num(issue) pr_url = github_pr_url(issue) issue_url = pr_url.replace("pulls", "issues") gh_issue_resp = github.get(issue_url) gh_issue_resp.raise_for_status() gh_issue = gh_issue_resp.json() sentry.client.extra_context({"github_issue": gh_issue}) if gh_issue["state"] == "closed": # nothing to do msg = "{key} was rejected, but PR #{num} was already closed".format( key=issue_key, num=pr_num ) print(msg, file=sys.stderr) return msg # Comment on the PR to explain to look at JIRA username = to_unicode(gh_issue["user"]["login"]) comment = {"body": ( "Hello @{username}: We are unable to continue with " "review of your submission at this time. Please see the " "associated JIRA ticket for more explanation.".format(username=username) )} comment_resp = github.post(issue_url + "/comments", json=comment) comment_resp.raise_for_status() # close the pull request on Github close_resp = github.patch(pr_url, json={"state": "closed"}) close_resp.raise_for_status() return "Closed PR #{num}".format(num=pr_num)
def jira_issue_rejected(issue): issue_key = to_unicode(issue["key"]) pr_num = github_pr_num(issue) pr_url = github_pr_url(issue) issue_url = pr_url.replace("pulls", "issues") gh_issue_resp = github.get(issue_url) gh_issue_resp.raise_for_status() gh_issue = gh_issue_resp.json() sentry.client.extra_context({"github_issue": gh_issue}) if gh_issue["state"] == "closed": # nothing to do msg = "{key} was rejected, but PR #{num} was already closed".format( key=issue_key, num=pr_num) print(msg, file=sys.stderr) return msg # Comment on the PR to explain to look at JIRA username = to_unicode(gh_issue["user"]["login"]) comment = { "body": ("Hello @{username}: We are unable to continue with " "review of your submission at this time. Please see the " "associated JIRA ticket for more explanation.".format( username=username)) } comment_resp = github.post(issue_url + "/comments", json=comment) comment_resp.raise_for_status() # close the pull request on Github close_resp = github.patch(pr_url, json={"state": "closed"}) close_resp.raise_for_status() return "Closed PR #{num}".format(num=pr_num)
def jira_issue_status_changed(issue, changelog, bugsnag_context=None): bugsnag_context = bugsnag_context or {} pr_num = github_pr_num(issue) pr_repo = github_pr_repo(issue) pr_url = github_pr_url(issue) issue_url = pr_url.replace("pulls", "issues") status_changelog = [item for item in changelog["items"] if item["field"] == "status"][0] old_status = status_changelog["fromString"] new_status = status_changelog["toString"] # get github issue gh_issue_resp = github.get(issue_url) if not gh_issue_resp.ok: raise requests.exceptions.RequestException(gh_issue_resp.text) gh_issue = gh_issue_resp.json() # get repo labels repo_labels_resp = github.get("/repos/{repo}/labels".format(repo=pr_repo)) if not repo_labels_resp.ok: raise requests.exceptions.RequestException(repo_labels_resp.text) # map of label name to label URL repo_labels = {l["name"]: l["url"] for l in repo_labels_resp.json()} # map of label name lowercased to label name in the case that it is on Github repo_labels_lower = {name.lower(): name for name in repo_labels} # Get all the existing labels on this PR pr_labels = [label["name"] for label in gh_issue["labels"]] print("old labels: {}".format(pr_labels), file=sys.stderr) # remove old status label old_status_label = repo_labels_lower.get(old_status.lower(), old_status) print("old status label: {}".format(old_status_label), file=sys.stderr) if old_status_label in pr_labels: pr_labels.remove(old_status_label) # add new status label new_status_label = repo_labels_lower[new_status.lower()] print("new status label: {}".format(new_status_label), file=sys.stderr) if new_status_label not in pr_labels: pr_labels.append(new_status_label) print("new labels: {}".format(pr_labels), file=sys.stderr) # Update labels on github update_label_resp = github.patch(issue_url, json={"labels": pr_labels}) if not update_label_resp.ok: raise requests.exceptions.RequestException(update_label_resp.text) return "Changed labels of PR #{num} to {labels}".format(num=pr_num, labels=pr_labels)
def jira_issue_rejected(issue, bugsnag_context=None): bugsnag_context = bugsnag_context or {} issue_key = to_unicode(issue["key"]) pr_num = github_pr_num(issue) pr_url = github_pr_url(issue) issue_url = pr_url.replace("pulls", "issues") gh_issue_resp = github.get(issue_url) if not gh_issue_resp.ok: raise requests.exceptions.RequestException(gh_issue_resp.text) gh_issue = gh_issue_resp.json() bugsnag_context["github_issue"] = gh_issue bugsnag.configure_request(meta_data=bugsnag_context) if gh_issue["state"] == "closed": # nothing to do msg = "{key} was rejected, but PR #{num} was already closed".format( key=issue_key, num=pr_num ) print(msg, file=sys.stderr) return msg # Comment on the PR to explain to look at JIRA username = to_unicode(gh_issue["user"]["login"]) comment = {"body": ( "Hello @{username}: We are unable to continue with " "review of your submission at this time. Please see the " "associated JIRA ticket for more explanation.".format(username=username) )} comment_resp = github.post(issue_url + "/comments", json=comment) # close the pull request on Github close_resp = github.patch(pr_url, json={"state": "closed"}) if not close_resp.ok or not comment_resp.ok: bugsnag_context['request_headers'] = close_resp.request.headers bugsnag_context['request_url'] = close_resp.request.url bugsnag_context['request_method'] = close_resp.request.method bugsnag.configure_request(meta_data=bugsnag_context) bug_text = '' if not close_resp.ok: bug_text += "Failed to close; " + close_resp.text if not comment_resp.ok: bug_text += "Failed to comment on the PR; " + comment_resp.text raise requests.exceptions.RequestException(bug_text) return "Closed PR #{num}".format(num=pr_num)
def pr_opened(pr, ignore_internal=True, check_contractor=True, bugsnag_context=None): bugsnag_context = bugsnag_context or {} user = pr["user"]["login"].decode('utf-8') repo = pr["base"]["repo"]["full_name"] num = pr["number"] if ignore_internal and is_internal_pull_request(pr): # not an open source pull request, don't create an issue for it print( "@{user} opened PR #{num} against {repo} (internal PR)".format( user=user, repo=repo, num=num, ), file=sys.stderr ) return "internal pull request" if check_contractor and is_contractor_pull_request(pr): # don't create a JIRA issue, but leave a comment comment = { "body": github_contractor_pr_comment(pr), } url = "/repos/{repo}/issues/{num}/comments".format( repo=repo, num=num, ) comment_resp = github.post(url, json=comment) comment_resp.raise_for_status() return "contractor pull request" issue_key = get_jira_issue_key(pr) if issue_key: msg = "Already created {key} for PR #{num} against {repo}".format( key=issue_key, num=pr["number"], repo=pr["base"]["repo"]["full_name"], ) print(msg, file=sys.stderr) return msg repo = pr["base"]["repo"]["full_name"].decode('utf-8') people = get_people_file() custom_fields = get_jira_custom_fields() if user in people: user_name = people[user].get("name", "") else: user_resp = github.get(pr["user"]["url"]) if user_resp.ok: user_name = user_resp.json().get("name", user) else: user_name = user # create an issue on JIRA! new_issue = { "fields": { "project": { "key": "OSPR", }, "issuetype": { "name": "Pull Request Review", }, "summary": pr["title"], "description": pr["body"], custom_fields["URL"]: pr["html_url"], custom_fields["PR Number"]: pr["number"], custom_fields["Repo"]: pr["base"]["repo"]["full_name"], custom_fields["Contributor Name"]: user_name, } } institution = people.get(user, {}).get("institution", None) if institution: new_issue["fields"][custom_fields["Customer"]] = [institution] bugsnag_context["new_issue"] = new_issue bugsnag.configure_request(meta_data=bugsnag_context) resp = jira.post("/rest/api/2/issue", json=new_issue) resp.raise_for_status() new_issue_body = resp.json() issue_key = new_issue_body["key"].decode('utf-8') bugsnag_context["new_issue"]["key"] = issue_key bugsnag.configure_request(meta_data=bugsnag_context) # add a comment to the Github pull request with a link to the JIRA issue comment = { "body": github_community_pr_comment(pr, new_issue_body, people), } url = "/repos/{repo}/issues/{num}/comments".format( repo=repo, num=pr["number"], ) comment_resp = github.post(url, json=comment) comment_resp.raise_for_status() # Add the "Needs Triage" label to the PR issue_url = "/repos/{repo}/issues/{num}".format(repo=repo, num=pr["number"]) label_resp = github.patch(issue_url, data=json.dumps({"labels": ["needs triage", "open-source-contribution"]})) label_resp.raise_for_status() print( "@{user} opened PR #{num} against {repo}, created {issue} to track it".format( user=user, repo=repo, num=pr["number"], issue=issue_key, ), file=sys.stderr ) return "created {key}".format(key=issue_key)
def pr_opened(pr, ignore_internal=True, check_contractor=True, bugsnag_context=None): bugsnag_context = bugsnag_context or {} user = pr["user"]["login"].decode('utf-8') repo = pr["base"]["repo"]["full_name"] num = pr["number"] if ignore_internal and is_internal_pull_request(pr): # not an open source pull request, don't create an issue for it print( "@{user} opened PR #{num} against {repo} (internal PR)".format( user=user, repo=repo, num=num, ), file=sys.stderr ) return "internal pull request" if check_contractor and is_contractor_pull_request(pr): # don't create a JIRA issue, but leave a comment comment = { "body": github_contractor_pr_comment(pr), } url = "/repos/{repo}/issues/{num}/comments".format( repo=repo, num=num, ) comment_resp = github.post(url, json=comment) if not comment_resp.ok: raise requests.exceptions.RequestException(comment_resp.text) return "contractor pull request" issue_key = get_jira_issue_key(pr) if issue_key: msg = "Already created {key} for PR #{num} against {repo}".format( key=issue_key, num=pr["number"], repo=pr["base"]["repo"]["full_name"], ) print(msg, file=sys.stderr) return msg repo = pr["base"]["repo"]["full_name"].decode('utf-8') people = get_people_file() custom_fields = get_jira_custom_fields() if user in people: user_name = people[user].get("name", "") else: user_resp = github.get(pr["user"]["url"]) if user_resp.ok: user_name = user_resp.json().get("name", user) else: user_name = user # create an issue on JIRA! new_issue = { "fields": { "project": { "key": "OSPR", }, "issuetype": { "name": "Pull Request Review", }, "summary": pr["title"], "description": pr["body"], custom_fields["URL"]: pr["html_url"], custom_fields["PR Number"]: pr["number"], custom_fields["Repo"]: pr["base"]["repo"]["full_name"], custom_fields["Contributor Name"]: user_name, } } institution = people.get(user, {}).get("institution", None) if institution: new_issue["fields"][custom_fields["Customer"]] = [institution] bugsnag_context["new_issue"] = new_issue bugsnag.configure_request(meta_data=bugsnag_context) resp = jira.post("/rest/api/2/issue", json=new_issue) if not resp.ok: raise requests.exceptions.RequestException(resp.text) new_issue_body = resp.json() issue_key = new_issue_body["key"].decode('utf-8') bugsnag_context["new_issue"]["key"] = issue_key bugsnag.configure_request(meta_data=bugsnag_context) # add a comment to the Github pull request with a link to the JIRA issue comment = { "body": github_community_pr_comment(pr, new_issue_body, people), } url = "/repos/{repo}/issues/{num}/comments".format( repo=repo, num=pr["number"], ) comment_resp = github.post(url, json=comment) if not comment_resp.ok: raise requests.exceptions.RequestException(comment_resp.text) # Add the "Needs Triage" label to the PR issue_url = "/repos/{repo}/issues/{num}".format(repo=repo, num=pr["number"]) label_resp = github.patch(issue_url, data=json.dumps({"labels": ["needs triage", "open-source-contribution"]})) if not label_resp.ok: raise requests.exceptions.RequestException(label_resp.text) print( "@{user} opened PR #{num} against {repo}, created {issue} to track it".format( user=user, repo=repo, num=pr["number"], issue=issue_key, ), file=sys.stderr ) return "created {key}".format(key=issue_key)
def jira_issue_updated(): """ Received an "issue updated" event from JIRA. https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview """ try: event = request.get_json() except ValueError: raise ValueError("Invalid JSON from JIRA: {data}".format( data=request.data.decode('utf-8') )) bugsnag_context = {"event": event} bugsnag.configure_request(meta_data=bugsnag_context) if app.debug: print(json.dumps(event), file=sys.stderr) issue_key = event["issue"]["key"].decode('utf-8') # is the issue an open source pull request? if event["issue"]["fields"]["project"]["key"] != "OSPR": # TODO: if the issue has just been moved from the OSPR project to a new project, # change the label to "engineering review". Need to figure out if we can tell that # the ticket has just moved projects. return "I don't care" # is there a changelog? changelog = event.get("changelog") if not changelog: # it was just someone adding a comment return "I don't care" # did the issue change status? status_changelog_items = [item for item in changelog["items"] if item["field"] == "status"] if len(status_changelog_items) == 0: return "I don't care" # construct Github API URL custom_fields = get_jira_custom_fields() pr_repo = event["issue"]["fields"].get(custom_fields["Repo"], "") pr_num = event["issue"]["fields"].get(custom_fields["PR Number"]) if not pr_repo or not pr_num: fail_msg = '{key} is missing "Repo" or "PR Number" fields'.format(key=issue_key) raise Exception(fail_msg) pr_num = int(pr_num) pr_url = "/repos/{repo}/pulls/{num}".format(repo=pr_repo, num=pr_num) # Need to use the Issues API for label manipulation issue_url = "/repos/{repo}/issues/{num}".format(repo=pr_repo, num=pr_num) old_status = status_changelog_items[0]["fromString"] new_status = status_changelog_items[0]["toString"] if new_status == "Rejected": issue_resp = github.get(issue_url) if not issue_resp.ok: raise requests.exceptions.RequestException(issue_resp.text) issue = issue_resp.json() if issue["state"] == "closed": # nothing to do msg = "{key} was rejected, but PR #{num} was already closed".format( key=issue_key, num=pr_num ) print(msg, file=sys.stderr) return msg # Comment on the PR to explain to look at JIRA username = issue["user"]["login"].decode('utf-8') comment = {"body": ( "Hello @{username}: We are unable to continue with " "review of your submission at this time. Please see the " "associated JIRA ticket for more explanation.".format(username=username) )} comment_resp = github.post(issue_url + "/comments", json=comment) # close the pull request on Github close_resp = github.patch(pr_url, json={"state": "closed"}) if not close_resp.ok or not comment_resp.ok: bugsnag_context['request_headers'] = close_resp.request.headers bugsnag_context['request_url'] = close_resp.request.url bugsnag_context['request_method'] = close_resp.request.method bugsnag.configure_request(meta_data=bugsnag_context) bug_text = '' if not close_resp.ok: bug_text += "Failed to close; " + close_resp.text if not comment_resp.ok: bug_text += "Failed to comment on the PR; " + comment_resp.text raise requests.exceptions.RequestException(bug_text) return "Closed PR #{num}".format(num=pr_num) elif new_status in STATUS_LABEL_DICT: # Get all the existing labels on this PR label_list = github.get(issue_url).json()["labels"] # Add in the label representing the new status - just add in the plain string label label_list.append(STATUS_LABEL_DICT[new_status][0]) # remove the label representing the old status, if it exists if old_status in STATUS_LABEL_DICT: # Sometimes labels are strings ("needs triage") whereas other times they're dictionaries # with the label name, color, and url defined. Have not pinned down when or why this happens. for old_label in STATUS_LABEL_DICT[old_status]: try: if isinstance(old_label, dict): old_label["url"] = old_label["url"].format(pr_repo=pr_repo) label_list.remove(old_label) except ValueError: print("PR {num} does not have label {old_label} to remove".format(num=pr_num, old_label=old_label)) print("PR {num} only has labels {labels}".format(num=pr_num, labels=label_list)) else: print("PR {num}: Successfully removed label {old_label}".format(num=pr_num, old_label=old_label)) break # Post the new set of labels to github label_resp = github.patch(issue_url, json={"labels": label_list}) if not label_resp.ok: raise requests.exceptions.RequestException(label_resp.text) return "Changed label of PR #{num} to {labels}".format(num=pr_num, labels=label_list) return "no change necessary"