Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/sentry/integrations/github/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from collections.abc import Mapping, Sequence
from operator import attrgetter
from typing import Any
from typing import Any, NoReturn

from django.urls import reverse

Expand All @@ -15,15 +15,31 @@
from sentry.issues.issue_occurrence import IssueOccurrence
from sentry.models.group import Group
from sentry.organizations.services.organization.service import organization_service
from sentry.shared_integrations.exceptions import ApiError, IntegrationError
from sentry.shared_integrations.exceptions import ApiError, IntegrationError, IntegrationFormError
from sentry.silo.base import all_silo_function
from sentry.users.models.identity import Identity
from sentry.users.models.user import User
from sentry.users.services.user import RpcUser
from sentry.utils.http import absolute_uri
from sentry.utils.strings import truncatechars

GITHUB_HALT_ERRORS = {
"Validation Failed",
}


class GitHubIssuesSpec(SourceCodeIssueIntegration):

def raise_error(self, exc: Exception, identity: Identity | None = None) -> NoReturn:
# Example validation error:
# sentry.shared_integrations.exceptions.ApiError: {"message":"Validation Failed","errors":[{"value":"xxx","resource":"Issue","field":"assignee","code":"invalid"}],"documentation_url":"https://docs.github.com/rest/issues/issues#create-an-issue","status":"422"}
if isinstance(exc, ApiError) and exc.json.get("message") in GITHUB_HALT_ERRORS:
# get errors, in the form key: value and conver to a list
errors = exc.json.get("errors")
parsed_errors = [f"{error['field']}: {error['code']}" for error in errors]
raise IntegrationFormError(parsed_errors)
raise super().raise_error(exc, identity=identity)

def make_external_key(self, data: Mapping[str, Any]) -> str:
return "{}#{}".format(data["repo"], data["key"])

Expand Down Expand Up @@ -196,7 +212,7 @@ def create_issue(self, data: Mapping[str, Any], **kwargs: Any) -> Mapping[str, A
try:
issue = client.create_issue(repo=repo, data=issue_data)
except ApiError as e:
raise IntegrationError(self.message_from_error(e))
raise self.raise_error(e)

return {
"key": issue["number"],
Expand Down
21 changes: 21 additions & 0 deletions tests/sentry/integrations/github/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from unittest.mock import patch

import orjson
import pytest
import responses
from django.test import RequestFactory
from django.utils import timezone
Expand All @@ -15,6 +16,7 @@
from sentry.integrations.models.external_issue import ExternalIssue
from sentry.integrations.services.integration import integration_service
from sentry.issues.grouptype import FeedbackGroup
from sentry.shared_integrations.exceptions import ApiError, IntegrationFormError
from sentry.silo.util import PROXY_BASE_URL_HEADER, PROXY_OI_HEADER, PROXY_SIGNATURE_HEADER
from sentry.testutils.cases import IntegratedApiTestCase, PerformanceIssueTestCase, TestCase
from sentry.testutils.helpers.datetime import before_now
Expand Down Expand Up @@ -258,6 +260,25 @@ def test_create_issue(self):
else:
self._check_proxying()

@responses.activate
@patch(
"sentry.integrations.github.client.GitHubBaseClient.create_issue",
side_effect=ApiError(
text='{"message": "Validation Failed", "errors": [{"value": "xxx", "resource": "Issue", "field": "assignee", "code": "invalid"}], "documentation_url": "https://docs.github.com/rest/issues/issues#create-an-issue", "status": "422"}',
),
)
def test_create_issue_with_validation_errors(self, mock_create_issue):
form_data = {
"repo": "getsentry/sentry",
"title": "hello",
"description": "This is the description",
}

with pytest.raises(IntegrationFormError) as e:
self.install.create_issue(form_data)

assert str(e.value) == str(IntegrationFormError("['assignee: invalid']"))

def test_performance_issues_content(self):
"""Test that a GitHub issue created from a performance issue has the expected title and description"""
event = self.create_performance_issue()
Expand Down
Loading