def github_community_pr_comment(pull_request, jira_issue, people=None, session=None): """ For a newly-created pull request from an open source contributor, write a welcoming comment on the pull request. The comment should: * contain a link to the JIRA issue * check for contributor agreement * check for AUTHORS entry * contain a link to our process documentation """ people = people or get_people_file(session=session) people = {user.lower(): values for user, values in people.items()} pr_author = pull_request["user"]["login"].decode("utf-8").lower() created_at = parse_date(pull_request["created_at"]).replace(tzinfo=None) # does the user have a valid, signed contributor agreement? has_signed_agreement = pr_author in people and people[pr_author].get("expires_on", date.max) > created_at.date() # is the user in the AUTHORS file? in_authors_file = False name = people.get(pr_author, {}).get("name", "") if name: authors_url = "https://raw.githubusercontent.com/{repo}/{branch}/AUTHORS".format( repo=pull_request["head"]["repo"]["full_name"].decode("utf-8"), branch=pull_request["head"]["ref"].decode("utf-8"), ) authors_resp = github.get(authors_url) if authors_resp.ok: authors_content = authors_resp.text if name in authors_content: in_authors_file = True return render_template( "github_community_pr_comment.md.j2", user=pull_request["user"]["login"].decode("utf-8"), repo=pull_request["base"]["repo"]["full_name"].decode("utf-8"), number=pull_request["number"], issue_key=jira_issue["key"].decode("utf-8"), has_signed_agreement=has_signed_agreement, in_authors_file=in_authors_file, )
def pull_request_opened(pull_request, ignore_internal=True, check_contractor=True): """ Process a pull request. This is called when a pull request is opened, or when the pull requests of a repo are re-scanned. By default, this function will ignore internal pull requests, and will add a comment to pull requests made by contractors (if if has not yet added a comment). However, this function can be called in such a way that it processes those pull requests anyway. Returns a 2-tuple. The first element in the tuple is the key of the JIRA issue associated with the pull request, if any, as a string. The second element in the tuple is a boolean indicating if this function did any work, such as making a JIRA issue or commenting on the pull request. """ pr = pull_request 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, session=github): # not an open source pull request, don't create an issue for it logger.info("@{user} opened PR #{num} against {repo} (internal PR)".format(user=user, repo=repo, num=num)) return None, False if check_contractor and is_contractor_pull_request(pr, session=github): # have we already left a contractor comment? if has_contractor_comment(pr, session=github): return None, False # 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 None, True 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"] ) logger.info(msg) return issue_key, False repo = pr["base"]["repo"]["full_name"].decode("utf-8") people = get_people_file(session=github) custom_fields = get_jira_custom_fields(jira) user_name = None if user in people: user_name = people[user].get("name", "") if not user_name: 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] sentry.client.extra_context({"new_issue": new_issue}) 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") new_issue["key"] = issue_key sentry.client.extra_context({"new_issue": new_issue}) # 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() logger.info( "@{user} opened PR #{num} against {repo}, created {issue} to track it".format( user=user, repo=repo, num=pr["number"], issue=issue_key ) ) return issue_key, True