def jira_rescan_users(): """ This task goes through all users on JIRA and ensures that they are assigned to the correct group based on the user's email address. It's meant to be run regularly: once an hour or so. """ # a mapping of group name to email domain domain_groups = { "edx-employees": "@edx.org", "clarice": "@claricetechnologies.com", "bnotions": "@bnotions.com", "qualcomm": ".qualcomm.com", "ubc": "@cs.ubc.ca", "ubc": "@ubc.ca", } if request.method == "GET": return render_template("jira_rescan_users.html", domain_groups=domain_groups) failures = defaultdict(dict) requested_group = request.form.get("group") if requested_group: if requested_group not in domain_groups: resp = jsonify({"error": "Not found", "groups": domain_groups.keys()}) resp.status_code = 404 return resp requested_groups = {requested_group: domain_groups[requested_group]} else: requested_groups = domain_groups for groupname, domain in requested_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) bugsnag_context = { "groupname": groupname, "usernames_in_group": usernames_in_group, } bugsnag.configure_request(meta_data=bugsnag_context) 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: failures[groupname][username] = resp.text resp = jsonify(failures) resp.status_code = 502 if failures else 200 return resp
def issue_opened(issue, bugsnag_context=None): bugsnag_context = bugsnag_context or {} bugsnag_context = {"issue": issue} bugsnag.configure_request(meta_data=bugsnag_context) issue_key = to_unicode(issue["key"]) issue_url = URLObject(issue["self"]) transitioned = False if should_transition(issue): transitions_url = issue_url.with_path(issue_url.path + "/transitions") transitions_resp = jira_get(transitions_url) if not transitions_resp.ok: raise requests.exceptions.RequestException(transitions_resp.text) transitions = {t["name"]: t["id"] for t in transitions_resp.json()["transitions"]} if "Open" in transitions: new_status = "Open" elif "Design Backlog" in transitions: new_status = "Design Backlog" else: raise ValueError("No valid transition! Possibilities are {}".format(transitions.keys())) body = { "transition": { "id": transitions[new_status], } } transition_resp = jira.post(transitions_url, json=body) if not transition_resp.ok: raise requests.exceptions.RequestException(transition_resp.text) transitioned = True # log to stderr action = "Transitioned to Open" if transitioned else "ignored" print( "{key} created by {name} ({username}), {action}".format( key=issue_key, name=to_unicode(issue["fields"]["creator"]["displayName"]), username=to_unicode(issue["fields"]["creator"]["name"]), action="Transitioned to Open" if transitioned else "ignored", ), file=sys.stderr, ) return action
def issue_opened(issue): sentry.client.extra_context({"issue": issue}) issue_key = to_unicode(issue["key"]) issue_url = URLObject(issue["self"]) transitioned = False if should_transition(issue): # In JIRA, a "transition" is how an issue changes from one status # to another, like going from "Open" to "In Progress". The workflow # defines what transitions are allowed, and this API will tell us # what transitions are currently allowed by the workflow. # Ref: https://docs.atlassian.com/jira/REST/ondemand/#d2e4954 transitions_url = issue_url.with_path(issue_url.path + "/transitions") transitions_resp = jira_get(transitions_url) transitions_resp.raise_for_status() # This transforms the API response into a simple mapping from the # name of the transition (like "In Progress") to the ID of the transition. # Note that a transition may not have the same name as the state that it # goes to, so a transition to go from "Open" to "In Progress" may be # named something like "Start Work". transitions = { t["name"]: t["id"] for t in transitions_resp.json()["transitions"] } # We attempt to transition the issue into the "Open" state for the given project # (some projects use a different name), so look for a transition with the right name new_status = None action = None for state_name in ["Open", "Design Backlog", "To Do"]: if state_name in transitions: new_status = state_name action = "Transitioned to '{}'".format(state_name) if not new_status: # If it's an OSPR subtask (used by teams to manage reviews), transition to team backlog if to_unicode( issue["fields"]["project"] ["key"]) == "OSPR" and issue["fields"]["issuetype"]["subtask"]: new_status = "To Backlog" action = "Transitioned to 'To Backlog'" else: raise ValueError( "No valid transition! Possibilities are {}".format( transitions.keys())) # This creates a new API request to tell JIRA to move the issue from # one status to another using the specified transition. We have to # tell JIRA the transition ID, so we use that mapping we set up earlier. body = { "transition": { "id": transitions[new_status], } } transition_resp = jira.post(transitions_url, json=body) transition_resp.raise_for_status() transitioned = True # log to stderr if transitioned and not action: action = "Transitioned to Open" else: action = "ignored" print( "{key} created by {name} ({username}), {action}".format( key=issue_key, name=to_unicode(issue["fields"]["creator"]["displayName"]), username=to_unicode(issue["fields"]["creator"]["name"]), action="Transitioned to Open" if transitioned else "ignored", ), file=sys.stderr, ) return action
def issue_opened(issue, bugsnag_context=None): bugsnag_context = bugsnag_context or {} bugsnag_context = {"issue": issue} bugsnag.configure_request(meta_data=bugsnag_context) issue_key = to_unicode(issue["key"]) issue_url = URLObject(issue["self"]) transitioned = False if should_transition(issue): # In JIRA, a "transition" is how an issue changes from one status # to another, like going from "Open" to "In Progress". The workflow # defines what transitions are allowed, and this API will tell us # what transitions are currently allowed by the workflow. # Ref: https://docs.atlassian.com/jira/REST/ondemand/#d2e4954 transitions_url = issue_url.with_path(issue_url.path + "/transitions") transitions_resp = jira_get(transitions_url) if not transitions_resp.ok: raise requests.exceptions.RequestException(transitions_resp.text) # This transforms the API response into a simple mapping from the # name of the transition (like "In Progress") to the ID of the transition. # Note that a transition may not have the same name as the state that it # goes to, so a transition to go from "Open" to "In Progress" may be # named something like "Start Work". transitions = {t["name"]: t["id"] for t in transitions_resp.json()["transitions"]} # We attempt to transition the issue into the "Open" state for the given project # (some projects use a different name), so look for a transition with the right name new_status = None action = None for state_name in ["Open", "Design Backlog", "To Do"]: if state_name in transitions: new_status = state_name action = "Transitioned to '{}'".format(state_name) if not new_status: # If it's an OSPR subtask (used by teams to manage reviews), transition to team backlog if to_unicode(issue["fields"]["project"]["key"]) == "OSPR" and issue["fields"]["issuetype"]["subtask"]: new_status = "To Backlog" action = "Transitioned to 'To Backlog'" else: raise ValueError("No valid transition! Possibilities are {}".format(transitions.keys())) # This creates a new API request to tell JIRA to move the issue from # one status to another using the specified transition. We have to # tell JIRA the transition ID, so we use that mapping we set up earlier. body = { "transition": { "id": transitions[new_status], } } transition_resp = jira.post(transitions_url, json=body) if not transition_resp.ok: raise requests.exceptions.RequestException(transition_resp.text) transitioned = True # log to stderr if transitioned and not action: action = "Transitioned to Open" else: action = "ignored" print( "{key} created by {name} ({username}), {action}".format( key=issue_key, name=to_unicode(issue["fields"]["creator"]["displayName"]), username=to_unicode(issue["fields"]["creator"]["name"]), action="Transitioned to Open" if transitioned else "ignored", ), file=sys.stderr, ) return action
def jira_issue_created(): """ Received an "issue created" event from JIRA. https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview Ideally, this should be handled in a task queue, but we want to stay within Heroku's free plan, so it will be handled inline instead. (A worker dyno costs money.) """ try: event = request.get_json() except ValueError: raise ValueError( "Invalid JSON from JIRA: {data}".format(data=request.data)) bugsnag.configure_request(meta_data={"event": event}) if app.debug: print(json.dumps(event), file=sys.stderr) issue_key = event["issue"]["key"] issue_status = event["issue"]["fields"]["status"]["name"] project = event["issue"]["fields"]["project"]["key"] if issue_status != "Needs Triage": print( "{key} has status {status}, does not need to be processed".format( key=issue_key, status=issue_status, ), file=sys.stderr, ) return "issue does not need to be triaged" if project == "OSPR": # open source pull requests do not skip Needs Triage print( "{key} is an open source pull request, and does not need to be processed." .format(key=issue_key), file=sys.stderr, ) return "issue is OSPR" issue_url = URLObject(event["issue"]["self"]) user_url = URLObject(event["user"]["self"]) user_url = user_url.set_query_param("expand", "groups") user_resp = jira_get(user_url) if not user_resp.ok: raise requests.exceptions.RequestException(user_resp.text) user = user_resp.json() groups = {g["name"]: g["self"] for g in user["groups"]["items"]} # skip "Needs Triage" if bug was created by edX employee transitioned = False if "edx-employees" in groups: transitions_url = issue_url.with_path(issue_url.path + "/transitions") transitions_resp = jira_get(transitions_url) if not transitions_resp.ok: raise requests.exceptions.RequestException(transitions_resp.text) transitions = { t["name"]: t["id"] for t in transitions_resp.json()["transitions"] } if "Open" in transitions: new_status = "Open" elif "Design Backlog" in transitions: new_status = "Design Backlog" else: raise ValueError( "No valid transition! Possibilities are {}".format( transitions.keys())) body = { "transition": { "id": transitions[new_status], } } transition_resp = jira.post(transitions_url, data=json.dumps(body)) if not transition_resp.ok: raise requests.exceptions.RequestException(transition_resp.text) transitioned = True # log to stderr print( "{key} created by {name} ({username}), {action}".format( key=issue_key, name=event["user"]["displayName"], username=event["user"]["name"], action="Transitioned to Open" if transitioned else "ignored", ), file=sys.stderr, ) return "Processed"
def pr_opened(pr, bugsnag_context=None): bugsnag_context = bugsnag_context or {} user = pr["user"]["login"] repo = pr["base"]["repo"]["full_name"] people = get_people_file() if user in people and people[user].get("institution", "") == "edX": # 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=pr["base"]["repo"]["full_name"], num=pr["number"] ), file=sys.stderr ) return "internal pull request" field_resp = jira.get("/rest/api/2/field") if not field_resp.ok: raise requests.exceptions.RequestException(field_resp.text) field_map = dict(pop_dict_id(f) for f in field_resp.json()) custom_fields = { value["name"]: id for id, value in field_map.items() if value["custom"] } 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", data=json.dumps(new_issue)) if not resp.ok: raise requests.exceptions.RequestException(resp.text) new_issue_body = resp.json() bugsnag_context["new_issue"]["key"] = new_issue_body["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_pr_comment(pr, new_issue_body, people), } url = "/repos/{repo}/issues/{num}/comments".format( repo=repo, num=pr["number"], ) comment_resp = github.post(url, data=json.dumps(comment)) if not comment_resp.ok: raise requests.exceptions.RequestException(comment_resp.text) print( "@{user} opened PR #{num} against {repo}, created {issue} to track it".format( user=user, repo=repo, num=pr["number"], issue=new_issue_body["key"] ), file=sys.stderr ) return "created!"
def pr_closed(pr, bugsnag_context=None): bugsnag_context = bugsnag_context or {} repo = pr["base"]["repo"]["full_name"] merged = pr["merged"] issue_key = get_jira_issue_key(pr) if not issue_key: print( "Couldn't find JIRA issue for PR #{num} against {repo}".format( num=pr["number"], repo=repo, ), file=sys.stderr ) return "no JIRA issue :(" bugsnag_context["jira_key"] = issue_key bugsnag.configure_request(meta_data=bugsnag_context) # 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) if not transitions_resp.ok: raise requests.exceptions.RequestException(transitions_resp.text) transitions = transitions_resp.json()["transitions"] bugsnag_context["transitions"] = transitions bugsnag.configure_request(meta_data=bugsnag_context) 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) if not issue_resp.ok: raise requests.exceptions.RequestException(issue_resp.text) issue = issue_resp.json() bugsnag_context["jira_issue"] = issue bugsnag.configure_request(meta_data=bugsnag_context) if issue["fields"]["status"]["name"] == transition_name: msg = "{key} is already in status {status}".format( key=issue_key, status=transition_name ) print(msg, file=sys.stderr) return "nothing to do!" fail_msg = ( "{key} cannot be transitioned directly to status {status}. " "Valid status transitions are: {valid}".format( key=issue_key, status=transition_name, valid=set(t["to"]["name"] for t in transitions), ) ) assert transition_id, fail_msg transition_resp = jira.post(transition_url, data=json.dumps({ "transition": { "id": transition_id, } })) if not transition_resp.ok: raise requests.exceptions.RequestException(transition_resp.text) print( "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", ), file=sys.stderr ) return "closed!"
def pr_closed(pr, bugsnag_context=None): bugsnag_context = bugsnag_context or {} repo = pr["base"]["repo"]["full_name"].decode('utf-8') merged = pr["merged"] issue_key = get_jira_issue_key(pr) if not issue_key: print( "Couldn't find JIRA issue for PR #{num} against {repo}".format( num=pr["number"], repo=repo, ), file=sys.stderr ) return "no JIRA issue :(" bugsnag_context["jira_key"] = issue_key bugsnag.configure_request(meta_data=bugsnag_context) # 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"] bugsnag_context["transitions"] = transitions bugsnag.configure_request(meta_data=bugsnag_context) 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() bugsnag_context["jira_issue"] = issue bugsnag.configure_request(meta_data=bugsnag_context) 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 ) print(msg, file=sys.stderr) return "nothing to do!" # 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() print( "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", ), file=sys.stderr ) return "closed!"
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 issue_opened(issue, bugsnag_context=None): bugsnag_context = bugsnag_context or {} bugsnag_context = {"issue": issue} bugsnag.configure_request(meta_data=bugsnag_context) issue_key = issue["key"].decode('utf-8') issue_status = issue["fields"]["status"]["name"].decode('utf-8') project = issue["fields"]["project"]["key"].decode('utf-8') if issue_status != "Needs Triage": print( "{key} has status {status}, does not need to be processed".format( key=issue_key, status=issue_status, ), file=sys.stderr, ) return "issue does not need to be triaged" if project == "OSPR": # open source pull requests do not skip Needs Triage print( "{key} is an open source pull request, and does not need to be processed.".format( key=issue_key ), file=sys.stderr, ) return "issue is OSPR" issue_url = URLObject(issue["self"]) user_url = URLObject(issue["fields"]["creator"]["self"]) user_url = user_url.set_query_param("expand", "groups") user_resp = jira_get(user_url) if not user_resp.ok: raise requests.exceptions.RequestException(user_resp.text) user = user_resp.json() groups = {g["name"]: g["self"] for g in user["groups"]["items"]} # skip "Needs Triage" if bug was created by edX employee transitioned = False if "edx-employees" in groups: transitions_url = issue_url.with_path(issue_url.path + "/transitions") transitions_resp = jira_get(transitions_url) if not transitions_resp.ok: raise requests.exceptions.RequestException(transitions_resp.text) transitions = {t["name"]: t["id"] for t in transitions_resp.json()["transitions"]} if "Open" in transitions: new_status = "Open" elif "Design Backlog" in transitions: new_status = "Design Backlog" else: raise ValueError("No valid transition! Possibilities are {}".format(transitions.keys())) body = { "transition": { "id": transitions[new_status], } } transition_resp = jira.post(transitions_url, json=body) if not transition_resp.ok: raise requests.exceptions.RequestException(transition_resp.text) transitioned = True try: name = issue["fields"]["creator"]["displayName"].decode('utf-8') except: bugsnag_context["name_type"] = type(issue["fields"]["creator"]["displayName"]) bugsnag.configure_request(meta_data=bugsnag_context) raise # log to stderr action = "Transitioned to Open" if transitioned else "ignored" print( "{key} created by {name} ({username}), {action}".format( key=issue_key, name=name, username=issue["fields"]["creator"]["name"].decode('utf-8'), action="Transitioned to Open" if transitioned else "ignored", ), file=sys.stderr, ) return action
def jira_issue_created(): """ Received an "issue created" event from JIRA. https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview Ideally, this should be handled in a task queue, but we want to stay within Heroku's free plan, so it will be handled inline instead. (A worker dyno costs money.) """ try: event = request.get_json() except ValueError: raise ValueError("Invalid JSON from JIRA: {data}".format(data=request.data)) bugsnag.configure_request(meta_data={"event": event}) if app.debug: print(json.dumps(event), file=sys.stderr) issue_key = event["issue"]["key"] issue_status = event["issue"]["fields"]["status"]["name"] project = event["issue"]["fields"]["project"]["key"] if issue_status != "Needs Triage": print( "{key} has status {status}, does not need to be processed".format( key=issue_key, status=issue_status, ), file=sys.stderr, ) return "issue does not need to be triaged" if project == "OSPR": # open source pull requests do not skip Needs Triage print( "{key} is an open source pull request, and does not need to be processed.".format( key=issue_key ), file=sys.stderr, ) return "issue is OSPR" issue_url = URLObject(event["issue"]["self"]) user_url = URLObject(event["user"]["self"]) user_url = user_url.set_query_param("expand", "groups") user_resp = jira_get(user_url) if not user_resp.ok: raise requests.exceptions.RequestException(user_resp.text) user = user_resp.json() groups = {g["name"]: g["self"] for g in user["groups"]["items"]} # skip "Needs Triage" if bug was created by edX employee transitioned = False if "edx-employees" in groups: transitions_url = issue_url.with_path(issue_url.path + "/transitions") transitions_resp = jira_get(transitions_url) if not transitions_resp.ok: raise requests.exceptions.RequestException(transitions_resp.text) transitions = {t["name"]: t["id"] for t in transitions_resp.json()["transitions"]} if "Open" in transitions: new_status = "Open" elif "Design Backlog" in transitions: new_status = "Design Backlog" else: raise ValueError("No valid transition! Possibilities are {}".format(transitions.keys())) body = { "transition": { "id": transitions[new_status], } } transition_resp = jira.post(transitions_url, data=json.dumps(body)) if not transition_resp.ok: raise requests.exceptions.RequestException(transition_resp.text) transitioned = True # log to stderr print( "{key} created by {name} ({username}), {action}".format( key=issue_key, name=event["user"]["displayName"], username=event["user"]["name"], action="Transitioned to Open" if transitioned else "ignored", ), file=sys.stderr, ) return "Processed"