diff --git a/src/sentry/integrations/bitbucket/issues.py b/src/sentry/integrations/bitbucket/issues.py index c5ff0429d2506..50880706e9e6c 100644 --- a/src/sentry/integrations/bitbucket/issues.py +++ b/src/sentry/integrations/bitbucket/issues.py @@ -171,3 +171,10 @@ def after_link_issue(self, external_issue, **kwargs): ) except ApiError as e: self.raise_error(e) + + def search_issues(self, query: str | None, **kwargs) -> dict[str, Any]: + client = self.get_client() + repo = kwargs["repo"] + resp = client.search_issues(repo, query) + assert isinstance(resp, dict) + return resp diff --git a/src/sentry/integrations/bitbucket/search.py b/src/sentry/integrations/bitbucket/search.py index eb3762be6e1c9..487fed7dba881 100644 --- a/src/sentry/integrations/bitbucket/search.py +++ b/src/sentry/integrations/bitbucket/search.py @@ -48,7 +48,7 @@ def get(self, request: Request, organization, integration_id, **kwds) -> Respons full_query = f'title~"{query}"' try: - resp = installation.get_client().search_issues(repo, full_query) + resp = installation.search_issues(query=full_query, repo=repo) except ApiError as e: if "no issue tracker" in str(e): logger.info( diff --git a/src/sentry/integrations/example/integration.py b/src/sentry/integrations/example/integration.py index cf6ae1a8f9782..9f7c74a2e0a76 100644 --- a/src/sentry/integrations/example/integration.py +++ b/src/sentry/integrations/example/integration.py @@ -191,6 +191,9 @@ def extract_source_path_from_source_url(self, repo: Repository, url: str) -> str def has_repo_access(self, repo: RpcRepository) -> bool: return False + def search_issues(self, query: str | None, **kwargs): + return [] + class ExampleIntegrationProvider(IntegrationProvider): """ diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index e57a0bb092822..f1f79cfb102eb 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -296,9 +296,10 @@ def get_trees_for_org(self, cache_seconds: int = 3600 * 24) -> dict[str, RepoTre return trees - # TODO(cathy): define in issue ABC - def search_issues(self, query: str) -> Mapping[str, Sequence[Mapping[str, Any]]]: - return self.get_client().search_issues(query) + def search_issues(self, query: str | None, **kwargs) -> dict[str, Any]: + resp = self.get_client().search_issues(query) + assert isinstance(resp, dict) + return resp class GitHubIntegrationProvider(IntegrationProvider): diff --git a/src/sentry/integrations/github_enterprise/integration.py b/src/sentry/integrations/github_enterprise/integration.py index cd56becc59f26..31bac578fb1da 100644 --- a/src/sentry/integrations/github_enterprise/integration.py +++ b/src/sentry/integrations/github_enterprise/integration.py @@ -203,7 +203,7 @@ def extract_branch_from_source_url(self, repo: Repository, url: str) -> str: def extract_source_path_from_source_url(self, repo: Repository, url: str) -> str: raise IntegrationFeatureNotImplementedError - def search_issues(self, query): + def search_issues(self, query: str | None, **kwargs): return self.get_client().search_issues(query) def has_repo_access(self, repo: RpcRepository) -> bool: diff --git a/src/sentry/integrations/gitlab/integration.py b/src/sentry/integrations/gitlab/integration.py index 7b687c32fe342..f15dde43ade90 100644 --- a/src/sentry/integrations/gitlab/integration.py +++ b/src/sentry/integrations/gitlab/integration.py @@ -1,5 +1,6 @@ from __future__ import annotations +from typing import Any from urllib.parse import urlparse from django import forms @@ -172,9 +173,13 @@ def search_projects(self, query): return client.search_projects(group_id, query) # TODO(cathy): define in issue ABC - def search_issues(self, project_id, query, iids): + def search_issues(self, query: str | None, **kwargs) -> list[dict[str, Any]]: client = self.get_client() - return client.search_project_issues(project_id, query, iids) + project_id = kwargs["project_id"] + iids = kwargs["iids"] + resp = client.search_project_issues(project_id, query, iids) + assert isinstance(resp, list) + return resp class InstallationForm(forms.Form): diff --git a/src/sentry/integrations/jira/integration.py b/src/sentry/integrations/jira/integration.py index 3117ccc89de88..5374176c9a3a2 100644 --- a/src/sentry/integrations/jira/integration.py +++ b/src/sentry/integrations/jira/integration.py @@ -451,9 +451,11 @@ def update_comment(self, issue_id, user_id, group_note): issue_id, group_note.data["external_id"], quoted_comment ) - def search_issues(self, query): + def search_issues(self, query: str | None, **kwargs) -> dict[str, Any]: try: - return self.get_client().search_issues(query) + resp = self.get_client().search_issues(query) + assert isinstance(resp, dict) + return resp except ApiError as e: self.raise_error(e) diff --git a/src/sentry/integrations/jira_server/integration.py b/src/sentry/integrations/jira_server/integration.py index c85d3fa4ff6c0..66f3ee72e7f5c 100644 --- a/src/sentry/integrations/jira_server/integration.py +++ b/src/sentry/integrations/jira_server/integration.py @@ -572,9 +572,11 @@ def update_comment(self, issue_id, user_id, group_note): issue_id, group_note.data["external_id"], quoted_comment ) - def search_issues(self, query): + def search_issues(self, query: str | None, **kwargs) -> dict[str, Any]: try: - return self.get_client().search_issues(query) + resp = self.get_client().search_issues(query) + assert isinstance(resp, dict) + return resp except ApiError as e: self.raise_error(e) diff --git a/src/sentry/integrations/jira_server/search.py b/src/sentry/integrations/jira_server/search.py index 7eb2615f417f9..69b0f7b0d8e96 100644 --- a/src/sentry/integrations/jira_server/search.py +++ b/src/sentry/integrations/jira_server/search.py @@ -8,6 +8,7 @@ from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import control_silo_endpoint from sentry.integrations.api.bases.integration import IntegrationEndpoint +from sentry.integrations.jira_server.integration import JiraServerIntegration from sentry.integrations.models.integration import Integration from sentry.organizations.services.organization import RpcOrganization from sentry.shared_integrations.exceptions import ApiError, ApiUnauthorized, IntegrationError @@ -23,7 +24,7 @@ class JiraServerSearchEndpoint(IntegrationEndpoint): } provider = "jira_server" - def _get_integration(self, organization, integration_id): + def _get_integration(self, organization, integration_id) -> Integration: return Integration.objects.get( organizationintegration__organization_id=organization.id, id=integration_id, @@ -38,6 +39,8 @@ def get( except Integration.DoesNotExist: return Response(status=404) installation = integration.get_installation(organization.id) + + assert isinstance(installation, JiraServerIntegration), installation jira_client = installation.get_client() field = request.GET.get("field") diff --git a/src/sentry/integrations/mixins/issues.py b/src/sentry/integrations/mixins/issues.py index 6ad6ed9fc9e35..9a77dd4d89f9e 100644 --- a/src/sentry/integrations/mixins/issues.py +++ b/src/sentry/integrations/mixins/issues.py @@ -303,6 +303,10 @@ def get_issue(self, issue_id, **kwargs): """ raise NotImplementedError + @abstractmethod + def search_issues(self, query: str | None, **kwargs) -> list[dict[str, Any]] | dict[str, Any]: + raise NotImplementedError + def after_link_issue(self, external_issue, **kwargs): """ Takes the external issue that has been linked via `get_issue`. diff --git a/src/sentry/integrations/vsts/issues.py b/src/sentry/integrations/vsts/issues.py index 30ec1049741be..7e897f813d13c 100644 --- a/src/sentry/integrations/vsts/issues.py +++ b/src/sentry/integrations/vsts/issues.py @@ -11,7 +11,7 @@ from sentry.integrations.services.integration import integration_service from sentry.integrations.source_code_management.issues import SourceCodeIssueIntegration from sentry.models.activity import Activity -from sentry.shared_integrations.exceptions import ApiError, ApiUnauthorized +from sentry.shared_integrations.exceptions import ApiError, ApiUnauthorized, IntegrationError from sentry.silo.base import all_silo_function from sentry.users.services.user import RpcUser from sentry.users.services.user.service import user_service @@ -356,3 +356,16 @@ def create_comment_attribution(self, user_id: int, comment_text: str) -> str: def update_comment(self, issue_id: int, user_id: int, group_note: str) -> None: # Azure does not support updating comments. pass + + def search_issues(self, query: str | None, **kwargs) -> dict[str, Any]: + client = self.get_client() + + integration = integration_service.get_integration( + integration_id=self.org_integration.integration_id + ) + if not integration: + raise IntegrationError("Azure DevOps integration not found") + + resp = client.search_issues(query=query, account_name=integration.name) + assert isinstance(resp, dict) + return resp diff --git a/src/sentry/integrations/vsts/search.py b/src/sentry/integrations/vsts/search.py index 50a74e7cc9af5..51c9b19a1815e 100644 --- a/src/sentry/integrations/vsts/search.py +++ b/src/sentry/integrations/vsts/search.py @@ -9,6 +9,7 @@ from sentry.hybridcloud.rpc import coerce_id_from from sentry.integrations.api.bases.integration import IntegrationEndpoint from sentry.integrations.models.integration import Integration +from sentry.integrations.vsts.integration import VstsIntegration from sentry.organizations.services.organization import RpcOrganization @@ -39,12 +40,13 @@ def get( return Response({"detail": "query is a required parameter"}, status=400) installation = integration.get_installation(organization.id) + assert isinstance(installation, VstsIntegration), installation if field == "externalIssue": if not query: return Response([]) - resp = installation.get_client().search_issues(integration.name, query) + resp = installation.search_issues(query=query) return Response( [ {