def get_assignees(payload: WildValue) -> Union[List[WildValue], WildValue]: assignee_details = payload.get("assignees") if not assignee_details: single_assignee_details = payload.get("assignee") if not single_assignee_details: transformed_assignee_details = [] else: transformed_assignee_details = [single_assignee_details] return transformed_assignee_details return assignee_details
def get_push_tag_body(payload: WildValue, change: WildValue) -> str: if change.get("new"): tag = change["new"] action = "pushed" elif change.get("old"): tag = change["old"] action = "removed" return get_push_tag_event_message( get_actor_info(payload), tag["name"].tame(check_string), tag_url=tag["links"]["html"]["href"].tame(check_string), action=action, )
def get_event(request: HttpRequest, payload: WildValue, branches: Optional[str]) -> Optional[str]: event = validate_extract_webhook_http_header(request, "X_GITLAB_EVENT", "GitLab") if event == "System Hook": # Convert the event name to a GitLab event title event_name = payload.get("event_name", payload["object_kind"]).tame(check_string) event = event_name.split("__")[0].replace("_", " ").title() event = f"{event} Hook" if event in [ "Confidential Issue Hook", "Issue Hook", "Merge Request Hook", "Wiki Page Hook" ]: action = payload["object_attributes"].get("action", "open").tame(check_string) event = f"{event} {action}" elif event in ["Confidential Note Hook", "Note Hook"]: action = payload["object_attributes"]["noteable_type"].tame( check_string) event = f"{event} {action}" elif event == "Push Hook": if branches is not None: branch = get_branch_name(payload) if branches.find(branch) == -1: return None if event in list(EVENT_FUNCTION_MAPPER.keys()): return event raise UnsupportedWebhookEventType(event)
def get_push_event_body(payload: WildValue) -> str: after = payload.get("after") if after: stringified_after = after.tame(check_string) if stringified_after == EMPTY_SHA: return get_remove_branch_event_body(payload) return get_normal_push_event_body(payload)
def render_block(block: WildValue) -> str: # https://api.slack.com/reference/block-kit/blocks block_type = block["type"].tame( check_string_in([ "actions", "context", "divider", "header", "image", "input", "section" ])) if block_type == "actions": # Unhandled return "" elif block_type == "context" and block.get("elements"): pieces = [] # Slack renders these pieces left-to-right, packed in as # closely as possible. We just render them above each other, # for simplicity. for element in block["elements"]: element_type = element["type"].tame( check_string_in(["image", "plain_text", "mrkdwn"])) if element_type == "image": pieces.append(render_block_element(element)) else: pieces.append(element.tame(check_text_block())) return "\n\n".join(piece.strip() for piece in pieces if piece.strip() != "") elif block_type == "divider": return "----" elif block_type == "header": return "## " + block["text"].tame( check_text_block(plain_text_only=True)) elif block_type == "image": image_url = block["image_url"].tame(check_url) alt_text = block["alt_text"].tame(check_string) if "title" in block: alt_text = block["title"].tame( check_text_block(plain_text_only=True)) return f"[{alt_text}]({image_url})" elif block_type == "input": # Unhandled pass elif block_type == "section": pieces = [] if "text" in block: pieces.append(block["text"].tame(check_text_block())) if "accessory" in block: pieces.append(render_block_element(block["accessory"])) if "fields" in block: # TODO -- these should be rendered in two columns, # left-to-right. We could render them sequentially, # except some may be Title1 / Title2 / value1 / value2, # which would be nonsensical when rendered sequentially. pass return "\n\n".join(piece.strip() for piece in pieces if piece.strip() != "") return ""
def api_bitbucket_webhook( request: HttpRequest, user_profile: UserProfile, payload: WildValue = REQ(converter=to_wild_value), branches: Optional[str] = REQ(default=None), ) -> HttpResponse: repository = payload["repository"] commits = [ { "name": commit["author"].tame(check_string) if "author" in commit else payload.get("user", "Someone").tame(check_string), "sha": commit["raw_node"].tame(check_string), "message": commit["message"].tame(check_string), "url": "{}{}commits/{}".format( payload["canon_url"].tame(check_string), repository["absolute_url"].tame(check_string), commit["raw_node"].tame(check_string), ), } for commit in payload["commits"] ] if len(commits) == 0: # Bitbucket doesn't give us enough information to really give # a useful message :/ subject = repository["name"].tame(check_string) content = "{} [force pushed]({}).".format( payload.get("user", "Someone").tame(check_string), payload["canon_url"].tame(check_string) + repository["absolute_url"].tame(check_string), ) else: branch = payload["commits"][-1]["branch"].tame(check_string) if branches is not None and branches.find(branch) == -1: return json_success(request) committer = payload.get("user", "Someone").tame(check_string) content = get_push_commits_event_message(committer, None, branch, commits) subject = TOPIC_WITH_BRANCH_TEMPLATE.format( repo=repository["name"].tame(check_string), branch=branch ) check_send_webhook_message(request, user_profile, subject, content, unquote_url_parameters=True) return json_success(request)
def extract_friendly_name(payload: WildValue) -> str: tentative_job_name = payload.get("friendly_name", "").tame(check_string) if not tentative_job_name: url = payload["url"].tame(check_string) segments = url.split("/") tentative_job_name = segments[-3] if tentative_job_name == "jobs": tentative_job_name = "Job" return tentative_job_name
def get_update_name_body(payload: WildValue, action: WildValue, entity: str) -> str: name = action["changes"]["name"] kwargs = { "entity": entity, "new": name["new"].tame(check_string), "old": name["old"].tame(check_string), "name_template": get_name_template(entity).format( name=action["name"].tame(check_string), app_url=action.get("app_url").tame(check_none_or(check_string)), ), } return NAME_CHANGED_TEMPLATE.format(**kwargs)
def get_comment_added_body(payload: WildValue, action: WildValue, entity: str) -> str: actions = payload["actions"] kwargs = {"entity": entity} for action in actions: if action["id"] == payload["primary_id"]: kwargs["text"] = action["text"].tame(check_string) elif action["entity_type"] == entity: name_template = get_name_template(entity).format( name=action["name"].tame(check_string), app_url=action.get("app_url").tame(check_none_or(check_string)), ) kwargs["name_template"] = name_template return COMMENT_ADDED_TEMPLATE.format(**kwargs)
def get_subject_based_on_type(payload: WildValue, event: str) -> str: if "pull_request" in event: return TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format( repo=get_repository_name(payload), type="PR", id=payload["pull_request"]["number"].tame(check_int), title=payload["pull_request"]["title"].tame(check_string), ) elif event.startswith("issue"): return TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format( repo=get_repository_name(payload), type="issue", id=payload["issue"]["number"].tame(check_int), title=payload["issue"]["title"].tame(check_string), ) elif event.startswith("deployment"): return "{} / Deployment on {}".format( get_repository_name(payload), payload["deployment"]["environment"].tame(check_string), ) elif event == "membership": return "{} organization".format( payload["organization"]["login"].tame(check_string)) elif event == "team": return "team {}".format(payload["team"]["name"].tame(check_string)) elif event == "push_commits": return TOPIC_WITH_BRANCH_TEMPLATE.format( repo=get_repository_name(payload), branch=get_branch_name_from_ref(payload["ref"].tame(check_string)), ) elif event == "gollum": return TOPIC_WITH_BRANCH_TEMPLATE.format( repo=get_repository_name(payload), branch="wiki pages", ) elif event == "ping": if not payload.get("repository"): return get_organization_name(payload) elif event == "check_run": return f"{get_repository_name(payload)} / checks" elif event.startswith("discussion"): return TOPIC_FOR_DISCUSSION.format( repo=get_repository_name(payload), number=payload["discussion"]["number"].tame(check_int), title=payload["discussion"]["title"].tame(check_string), ) return get_repository_name(payload)
def get_update_archived_body(payload: WildValue, action: WildValue, entity: str) -> str: archived = action["changes"]["archived"] if archived["new"]: operation = "archived" else: operation = "unarchived" kwargs = { "entity": entity, "name_template": get_name_template(entity).format( name=action["name"].tame(check_string), app_url=action.get("app_url").tame(check_none_or(check_string)), ), "operation": operation, } return ARCHIVED_TEMPLATE.format(**kwargs)
def get_story_joined_label_list( payload: WildValue, action: WildValue, label_ids_added: List[int] ) -> str: labels = [] for label_id in label_ids_added: label_name = "" for action in payload["actions"]: if action["id"].tame(check_int) == label_id: label_name = action.get("name", "").tame(check_string) if label_name == "": reference = get_reference_by_id(payload, label_id) label_name = "" if reference is None else reference["name"].tame(check_string) labels.append(LABEL_TEMPLATE.format(name=label_name)) return ", ".join(labels)
def get_update_description_body(payload: WildValue, action: WildValue, entity: str) -> str: desc = action["changes"]["description"] kwargs = { "entity": entity, "new": desc["new"].tame(check_string), "old": desc["old"].tame(check_string), "name_template": get_name_template(entity).format( name=action["name"].tame(check_string), app_url=action.get("app_url").tame(check_none_or(check_string)), ), } if kwargs["new"] and kwargs["old"]: body = DESC_CHANGED_TEMPLATE.format(**kwargs) elif kwargs["new"]: body = NEW_DESC_ADDED_TEMPLATE.format(**kwargs) else: body = DESC_REMOVED_TEMPLATE.format(**kwargs) return body
def get_story_update_state_body(payload: WildValue, action: WildValue) -> str: workflow_state_id = action["changes"]["workflow_state_id"] references = payload["references"] state = {} for ref in references: if ref["id"].tame(check_string_or_int) == workflow_state_id["new"].tame(check_int): state["new"] = ref["name"].tame(check_string) if ref["id"].tame(check_string_or_int) == workflow_state_id["old"].tame(check_int): state["old"] = ref["name"].tame(check_string) kwargs = { "entity": "story", "new": state["new"], "old": state["old"], "name_template": STORY_NAME_TEMPLATE.format( name=action["name"].tame(check_string), app_url=action.get("app_url").tame(check_none_or(check_string)), ), } return STATE_CHANGED_TEMPLATE.format(**kwargs)
def get_tag_push_event_body(payload: WildValue) -> str: return get_push_tag_event_message( get_user_name(payload), get_tag_name(payload), action="pushed" if payload.get("checkout_sha") else "removed", )
def get_zulip_event_name( header_event: str, payload: WildValue, branches: Optional[str], ) -> Optional[str]: """ Usually, we return an event name that is a key in EVENT_FUNCTION_MAPPER. We return None for an event that we know we don't want to handle. """ if header_event == "pull_request": action = payload["action"].tame(check_string) if action in ("opened", "synchronize", "reopened", "edited"): return "opened_or_update_pull_request" if action in ("assigned", "unassigned"): return "assigned_or_unassigned_pull_request" if action == "closed": return "closed_pull_request" if action == "review_requested": return "pull_request_review_requested" if action == "ready_for_review": return "pull_request_ready_for_review" if action in ("locked", "unlocked"): return "locked_or_unlocked_pull_request" if action in ("auto_merge_enabled", "auto_merge_disabled"): return "pull_request_auto_merge" if action in IGNORED_PULL_REQUEST_ACTIONS: return None elif header_event == "push": if is_commit_push_event(payload): if branches is not None: branch = get_branch_name_from_ref( payload["ref"].tame(check_string)) if branches.find(branch) == -1: return None return "push_commits" else: return "push_tags" elif header_event == "check_run": if payload["check_run"]["status"].tame(check_string) != "completed": return None return header_event elif header_event == "team": action = payload["action"].tame(check_string) if action == "edited": return "team" if action in IGNORED_TEAM_ACTIONS: # no need to spam our logs, we just haven't implemented it yet return None else: # this means GH has actually added new actions since September 2020, # so it's a bit more cause for alarm raise UnsupportedWebhookEventType( f"unsupported team action {action}") elif header_event in list(EVENT_FUNCTION_MAPPER.keys()): return header_event elif header_event in IGNORED_EVENTS: return None complete_event = "{}:{}".format( header_event, payload.get("action", "???").tame(check_string)) # nocoverage raise UnsupportedWebhookEventType(complete_event)