diff --git a/src/sentry/integrations/jira/integration.py b/src/sentry/integrations/jira/integration.py index a0e20abcf8a222..441da6bf7c7b9d 100644 --- a/src/sentry/integrations/jira/integration.py +++ b/src/sentry/integrations/jira/integration.py @@ -15,6 +15,7 @@ from sentry import features from sentry.eventstore.models import GroupEvent +from sentry.exceptions import InvalidConfiguration from sentry.integrations.base import ( FeatureDescription, IntegrationData, @@ -1006,17 +1007,27 @@ def sync_assignee_outbound( extra={ "integration_id": external_issue.integration_id, "user_id": user.id, + "user_emails": user.emails, "issue_key": external_issue.key, + "organization_id": external_issue.organization_id, }, ) - return + if not user.emails: + raise InvalidConfiguration( + { + "email": "User must have a verified email on Sentry to sync assignee in Jira", + "help": "https://sentry.io/settings/account/emails", + } + ) + raise InvalidConfiguration({"email": "Unable to find the requested user"}) try: id_field = client.user_id_field() client.assign_issue(external_issue.key, jira_user and jira_user.get(id_field)) - except (ApiUnauthorized, ApiError): + except ApiError as e: # TODO(jess): do we want to email people about these types of failures? logger.info( "jira.failed-to-assign", + exc_info=e, extra={ "organization_id": external_issue.organization_id, "integration_id": external_issue.integration_id, @@ -1024,6 +1035,7 @@ def sync_assignee_outbound( "issue_key": external_issue.key, }, ) + raise def sync_status_outbound( self, external_issue: ExternalIssue, is_resolved: bool, project_id: int diff --git a/src/sentry/integrations/tasks/sync_assignee_outbound.py b/src/sentry/integrations/tasks/sync_assignee_outbound.py index 0d796c47d34d7b..d1b8f0eda09acb 100644 --- a/src/sentry/integrations/tasks/sync_assignee_outbound.py +++ b/src/sentry/integrations/tasks/sync_assignee_outbound.py @@ -2,6 +2,7 @@ from sentry import analytics, features from sentry.constants import ObjectStatus +from sentry.exceptions import InvalidConfiguration from sentry.integrations.errors import OrganizationIntegrationNotFound from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.models.integration import Integration @@ -12,7 +13,7 @@ from sentry.integrations.services.assignment_source import AssignmentSource from sentry.integrations.services.integration import integration_service from sentry.models.organization import Organization -from sentry.shared_integrations.exceptions import IntegrationError +from sentry.shared_integrations.exceptions import ApiUnauthorized, IntegrationError from sentry.silo.base import SiloMode from sentry.tasks.base import instrumented_task, retry from sentry.taskworker.config import TaskworkerConfig @@ -96,5 +97,5 @@ def sync_assignee_outbound( id=integration.id, organization_id=external_issue.organization_id, ) - except OrganizationIntegrationNotFound: - lifecycle.record_halt("organization_integration_not_found") + except (OrganizationIntegrationNotFound, ApiUnauthorized, InvalidConfiguration) as e: + lifecycle.record_halt(halt_reason=e) diff --git a/tests/sentry/integrations/jira/test_integration.py b/tests/sentry/integrations/jira/test_integration.py index afb19c471922eb..96bcbf70004245 100644 --- a/tests/sentry/integrations/jira/test_integration.py +++ b/tests/sentry/integrations/jira/test_integration.py @@ -9,6 +9,7 @@ from fixtures.integrations.jira.stub_client import StubJiraApiClient from fixtures.integrations.stub_service import StubService +from sentry.exceptions import InvalidConfiguration from sentry.integrations.jira.integration import JiraIntegrationProvider from sentry.integrations.jira.views import SALT from sentry.integrations.models.external_issue import ExternalIssue @@ -824,7 +825,8 @@ def test_sync_assignee_outbound_no_email(self): "https://example.atlassian.net/rest/api/2/user/assignable/search", json=[{"accountId": "deadbeef123", "displayName": "Dead Beef"}], ) - installation.sync_assignee_outbound(external_issue, user) + with pytest.raises(InvalidConfiguration): + installation.sync_assignee_outbound(external_issue, user) # No sync made as jira users don't have email addresses assert len(responses.calls) == 1 diff --git a/tests/sentry/integrations/tasks/test_sync_assignee_outbound.py b/tests/sentry/integrations/tasks/test_sync_assignee_outbound.py index 16d2c75c78154c..ea36b6ba958a44 100644 --- a/tests/sentry/integrations/tasks/test_sync_assignee_outbound.py +++ b/tests/sentry/integrations/tasks/test_sync_assignee_outbound.py @@ -2,12 +2,13 @@ import pytest +from sentry.integrations.errors import OrganizationIntegrationNotFound from sentry.integrations.example import ExampleIntegration from sentry.integrations.models import ExternalIssue, Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.integrations.tasks import sync_assignee_outbound from sentry.integrations.types import EventLifecycleOutcome -from sentry.testutils.asserts import assert_success_metric +from sentry.testutils.asserts import assert_halt_metric, assert_success_metric from sentry.testutils.cases import TestCase from sentry.testutils.silo import assume_test_silo_mode_of from sentry.users.services.user import RpcUser @@ -151,4 +152,6 @@ def test_missing_organization_integration(self, mock_sync_assignee, mock_record_ mock_sync_assignee.assert_not_called() assert mock_record_event.call_count == 2 - assert_success_metric(mock_record_event) + assert_halt_metric( + mock_record_event, OrganizationIntegrationNotFound("missing org_integration") + )