diff --git a/src/sentry/integrations/github/client.py b/src/sentry/integrations/github/client.py index 58462b29fbdb66..0abaa2dde22dc4 100644 --- a/src/sentry/integrations/github/client.py +++ b/src/sentry/integrations/github/client.py @@ -920,6 +920,13 @@ def get_labels(self, owner: str, repo: str) -> list[Any]: """ return self._get_with_pagination(f"/repos/{owner}/{repo}/labels") + def get_org_issue_types(self, owner: str) -> list[Any]: + """ + Fetches all issue types for an organization. + https://docs.github.com/en/rest/orgs/issue-types#list-issue-types-for-an-organization + """ + return self._get_with_pagination(f"/orgs/{owner}/issue-types") + def check_file(self, repo: Repository, path: str, version: str | None) -> object | None: return self.head_cached(path=f"/repos/{repo.name}/contents/{path}", params={"ref": version}) diff --git a/src/sentry/integrations/github/issues.py b/src/sentry/integrations/github/issues.py index b74cd1f02e3c2f..d15feeca4d806d 100644 --- a/src/sentry/integrations/github/issues.py +++ b/src/sentry/integrations/github/issues.py @@ -180,9 +180,14 @@ def get_create_issue_config( assignees = self.get_allowed_assignees(default_repo) if default_repo else [] labels: Sequence[tuple[str, str]] = [] + issue_types: Sequence[tuple[str, str]] = [] if default_repo: owner, repo = default_repo.split("/") labels = self.get_repo_labels(owner, repo) + try: + issue_types = self.get_org_issue_types(owner) + except IntegrationResourceNotFoundError: + issue_types = [] autocomplete_url = reverse( "sentry-integration-github-search", args=[org.slug, self.model.id] @@ -217,6 +222,15 @@ def get_create_issue_config( "required": False, "choices": labels, }, + { + "name": "type", + "label": "Type", + "default": "", + "type": "select", + "multiple": False, + "required": False, + "choices": issue_types, + }, ] def create_issue(self, data: Mapping[str, Any], **kwargs: Any) -> Mapping[str, Any]: @@ -253,6 +267,8 @@ def create_issue(self, data: Mapping[str, Any], **kwargs: Any) -> Mapping[str, A issue_data["assignee"] = data["assignee"] if data.get("labels"): issue_data["labels"] = data["labels"] + if data.get("type"): + issue_data["type"] = data["type"] try: issue = client.create_issue(repo=repo, data=issue_data) @@ -373,15 +389,34 @@ def get_repo_labels(self, owner: str, repo: str) -> Sequence[tuple[str, str]]: except Exception as e: self.raise_error(e) - def natural_sort_pair(pair: tuple[str, str]) -> list[str | int]: - return [ - int(text) if text.isdecimal() else text.lower() - for text in re.split("([0-9]+)", pair[0]) - ] - # sort alphabetically labels = tuple( - sorted([(label["name"], label["name"]) for label in response], key=natural_sort_pair) + sorted( + [(label["name"], label["name"]) for label in response], key=self.natural_sort_pair + ) ) return labels + + def get_org_issue_types(self, owner: str) -> Sequence[tuple[str, str]]: + client = self.get_client() + try: + response = client.get_org_issue_types(owner) + except Exception as e: + self.raise_error(e) + + # sort alphabetically + types = tuple( + sorted( + [(type_obj["name"], type_obj["name"]) for type_obj in response], + key=self.natural_sort_pair, + ) + ) + + return types + + def natural_sort_pair(self, pair: tuple[str, str]) -> list[str | int]: + return [ + int(text) if text.isdecimal() else text.lower() + for text in re.split("([0-9]+)", pair[0]) + ] diff --git a/tests/sentry/integrations/github/test_issues.py b/tests/sentry/integrations/github/test_issues.py index 8a6727cc3a58c3..8ca479be892085 100644 --- a/tests/sentry/integrations/github/test_issues.py +++ b/tests/sentry/integrations/github/test_issues.py @@ -87,9 +87,15 @@ def test_get_create_issue_config_without_group(self) -> None: ], ) + responses.add( + responses.GET, + "https://api.github.com/orgs/getsentry/issue-types", + json=[{"name": "bug"}, {"name": "task"}], + ) + install = self.install config = install.get_create_issue_config(None, self.user, params={}) - [repo_field, assignee_field, label_field] = config + repo_field, assignee_field, label_field = config[:3] assert repo_field["name"] == "repo" assert repo_field["type"] == "select" assert repo_field["label"] == "GitHub Repository" @@ -635,6 +641,11 @@ def test_repo_dropdown_choices(self) -> None: "https://api.github.com/repos/getsentry/sentry/labels", json=[{"name": "bug"}, {"name": "enhancement"}], ) + responses.add( + responses.GET, + "https://api.github.com/orgs/getsentry/issue-types", + json=[{"name": "bug"}, {"name": "task"}], + ) responses.add( responses.GET, @@ -787,6 +798,11 @@ def test_default_repo_create_fields(self) -> None: "https://api.github.com/repos/getsentry/sentry/labels", json=[{"name": "bug"}, {"name": "enhancement"}], ) + responses.add( + responses.GET, + "https://api.github.com/orgs/getsentry/issue-types", + json=[{"name": "bug"}, {"name": "task"}], + ) event = self.store_event( data={"event_id": "a" * 32, "timestamp": self.min_ago}, project_id=self.project.id )