Example #1
0
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)
Example #2
0
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)
Example #3
0
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)
Example #4
0
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)
Example #5
0
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)
Example #6
0
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)
Example #7
0
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"