def rescan_users(domain_groups): failures = defaultdict(dict) for groupname, domain in domain_groups.items(): users_in_group = jira_group_members(groupname, session=jira, debug=True) usernames_in_group = set(u["name"] for u in users_in_group) sentry.client.extra_context({ "groupname": groupname, "usernames_in_group": usernames_in_group, }) for user in jira_users(filter=domain, session=jira, debug=True): if not user["email"].endswith(domain): pass username = user["name"] if username not in usernames_in_group: # add the user to the group! resp = jira.post( "/rest/api/2/group/user?groupname={}".format(groupname), json={"name": username}, ) if not resp.ok: # Is this a failure saying that the user is already in # the group? If so, ignore it. nothing_to_do_msg = ( "Cannot add user '{username}', " "user is already a member of '{groupname}'" ).format(username=username, groupname=groupname) error = resp.json() if error.get("errorMessages", []) == [nothing_to_do_msg]: continue else: # it's some other kind of failure, so log it failures[groupname][username] = resp.text if failures: logger.error("Failures in trying to rescan JIRA users: {}".format(failures)) return failures
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
def pull_request_closed(pull_request): pr = pull_request repo = pr["base"]["repo"]["full_name"].decode("utf-8") merged = pr["merged"] issue_key = get_jira_issue_key(pr) if not issue_key: logger.info("Couldn't find JIRA issue for PR #{num} against {repo}".format(num=pr["number"], repo=repo)) return "no JIRA issue :(" sentry.client.extra_context({"jira_key": issue_key}) # close the issue on JIRA transition_url = "/rest/api/2/issue/{key}/transitions" "?expand=transitions.fields".format(key=issue_key) transitions_resp = jira.get(transition_url) transitions_resp.raise_for_status() transitions = transitions_resp.json()["transitions"] sentry.client.extra_context({"transitions": transitions}) transition_name = "Merged" if merged else "Rejected" transition_id = None for t in transitions: if t["to"]["name"] == transition_name: transition_id = t["id"] break if not transition_id: # maybe the issue is *already* in the right status? issue_url = "/rest/api/2/issue/{key}".format(key=issue_key) issue_resp = jira.get(issue_url) issue_resp.raise_for_status() issue = issue_resp.json() sentry.client.extra_context({"jira_issue": issue}) current_status = issue["fields"]["status"]["name"].decode("utf-8") if current_status == transition_name: msg = "{key} is already in status {status}".format(key=issue_key, status=transition_name) logger.info(msg) return False # nope, raise an error message fail_msg = ( "{key} cannot be transitioned directly from status {curr_status} " "to status {new_status}. Valid status transitions are: {valid}".format( key=issue_key, new_status=transition_name, curr_status=current_status, valid=", ".join(t["to"]["name"].decode("utf-8") for t in transitions), ) ) raise Exception(fail_msg) transition_resp = jira.post(transition_url, json={"transition": {"id": transition_id}}) transition_resp.raise_for_status() logger.info( "PR #{num} against {repo} was {action}, moving {issue} to status {status}".format( num=pr["number"], repo=repo, action="merged" if merged else "closed", issue=issue_key, status="Merged" if merged else "Rejected", ) ) return True