Skip to content

feat(eco): Adds new RPC method for surfacing externally linked issue summaries #96699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

GabeVillalobos
Copy link
Member

@GabeVillalobos GabeVillalobos commented Jul 29, 2025

Backstory

We've wanted to enable Jira multi-region support for a while (#80381), but have been blocked on a couple of different fronts:

  1. Webhooks only dispatch to a single region for simplicity.
  2. There's a context pane in our Jira configuration which surfaces issue data that is region-only, and therefore relies on proxy behavior. This can only be done safely when all integration installs are colocated within a single region.

Jira Context Pane

This is the pane we're populating in Jira with requests to the /issue/<external-issue-key/ URL in control:

image

Atlassian Docs

Why this change?

This is the first step, of many, to enable Jira multi-region support.

This new RPC is meant to back a new multi-region version of the Jira Issue Context pane, which will now:

  • Dispatch an RPC to each region containing an installation of the Jira integration identified in the request JWT.
  • Surface a combined list of issue link summaries, containing a title, date linked, and Issue link specific to the region.

The current pane, as-is, is rather cluttered, and will only get worse once we allow multiple issues from multiple organizations in multiple regions to link to the same ticket. This is meant to back an abbreviated version of the existing preview pane with links to the corresponding issue links.

Checklist for Jira Multi-Region Support (subject to change):

  • Add new RPC method to surface issue summaries. [We are here]
  • Implement a new Control-Silo endpoint for Jira to use which will perform region fanouts for issue data.
  • Change Jira Connect Metadata to use this new endpoint when populating the issue context pane, update the Jira Parser to disallow requests to the region for this.
  • Update the Jira parser to allow for multi-region installations, gated behind a user feature-flag
  • Test multi-region installations, ensure that everything is working as expected
  • Inevitable bug fixes for issue creation, linking
  • GA multi-region Jira feature flag
  • Deprecate the old region issue context endpoint and delete it.

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Jul 29, 2025
Copy link

codecov bot commented Jul 29, 2025

❌ 22 Tests Failed:

Tests completed Failed Passed Skipped
26806 22 26784 600
View the top 3 failed test(s) by shortest run time
tests.sentry.web.frontend.test_shared_group_details.SharedGroupDetailsTest::test_get_success
Stack Traces | 2.12s run time
#x1B[1m#x1B[.../web/frontend/test_shared_group_details.py#x1B[0m:66: in test_get_success
    response = self.client.get(f"/share/issue/{share.uuid}/", HTTP_HOST=self.org_domain)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:1124: in get
    response = super().get(
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:475: in get
    return self.generic(
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:671: in generic
    return self.request(**r)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:1087: in request
    self.check_exception(response)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:802: in check_exception
    raise exc_value
#x1B[1m#x1B[31m.venv/lib/python3.13.../core/handlers/exception.py#x1B[0m:55: in inner
    response = get_response(request)
#x1B[1m#x1B[31m.venv/lib/python3.13.../core/handlers/base.py#x1B[0m:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../views/generic/base.py#x1B[0m:104: in view
    return self.dispatch(request, *args, **kwargs)
#x1B[1m#x1B[.../sentry/silo/base.py#x1B[0m:158: in override
    return original_method(*args, **kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/utils/decorators.py#x1B[0m:48: in _wrapper
    return bound_method(*args, **kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../views/decorators/csrf.py#x1B[0m:65: in _view_wrapper
    return view_func(request, *args, **kwargs)
#x1B[1m#x1B[.../web/frontend/base.py#x1B[0m:417: in dispatch
    return self.handle(request, *args, **kwargs)
#x1B[1m#x1B[.../web/frontend/react_page.py#x1B[0m:207: in handle
    return self.handle_react(request, **kwargs)
#x1B[1m#x1B[.../web/frontend/react_page.py#x1B[0m:102: in handle_react
    for key, value in self.meta_tags(request, **kwargs).items()
#x1B[1m#x1B[.../web/frontend/shared_group_details.py#x1B[0m:18: in meta_tags
    group = issue_service.get_shared_for_org(slug=org_slug, share_id=share_id)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:354: in remote_method
    return dispatch_remote_call(
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:476: in dispatch_remote_call
    return remote_silo_call.dispatch(use_test_client)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:511: in dispatch
    serial_response = self._send_to_remote_silo(use_test_client)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:582: in _send_to_remote_silo
    self._raise_from_response_status_error(response)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:605: in _raise_from_response_status_error
    raise self._remote_exception(
#x1B[1m#x1B[31mE   sentry.hybridcloud.rpc.service.RpcRemoteException: issue.get_shared_for_org: Error invoking rpc at '.../rpc/issue/get_shared_for_org/': check error logs for more details#x1B[0m
tests.sentry.web.frontend.test_shared_group_details.SharedGroupDetailsTest::test_get_no_subdomain
Stack Traces | 2.24s run time
#x1B[1m#x1B[.../web/frontend/test_shared_group_details.py#x1B[0m:60: in test_get_no_subdomain
    response = self.client.get(f"/share/issue/{share.uuid}/")
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:1124: in get
    response = super().get(
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:475: in get
    return self.generic(
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:671: in generic
    return self.request(**r)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:1087: in request
    self.check_exception(response)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:802: in check_exception
    raise exc_value
#x1B[1m#x1B[31m.venv/lib/python3.13.../core/handlers/exception.py#x1B[0m:55: in inner
    response = get_response(request)
#x1B[1m#x1B[31m.venv/lib/python3.13.../core/handlers/base.py#x1B[0m:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../views/generic/base.py#x1B[0m:104: in view
    return self.dispatch(request, *args, **kwargs)
#x1B[1m#x1B[.../sentry/silo/base.py#x1B[0m:158: in override
    return original_method(*args, **kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/utils/decorators.py#x1B[0m:48: in _wrapper
    return bound_method(*args, **kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../views/decorators/csrf.py#x1B[0m:65: in _view_wrapper
    return view_func(request, *args, **kwargs)
#x1B[1m#x1B[.../web/frontend/base.py#x1B[0m:417: in dispatch
    return self.handle(request, *args, **kwargs)
#x1B[1m#x1B[.../web/frontend/react_page.py#x1B[0m:207: in handle
    return self.handle_react(request, **kwargs)
#x1B[1m#x1B[.../web/frontend/react_page.py#x1B[0m:102: in handle_react
    for key, value in self.meta_tags(request, **kwargs).items()
#x1B[1m#x1B[.../web/frontend/shared_group_details.py#x1B[0m:21: in meta_tags
    group = issue_service.get_shared_for_region(
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:354: in remote_method
    return dispatch_remote_call(
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:476: in dispatch_remote_call
    return remote_silo_call.dispatch(use_test_client)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:511: in dispatch
    serial_response = self._send_to_remote_silo(use_test_client)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:582: in _send_to_remote_silo
    self._raise_from_response_status_error(response)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:605: in _raise_from_response_status_error
    raise self._remote_exception(
#x1B[1m#x1B[31mE   sentry.hybridcloud.rpc.service.RpcRemoteException: issue.get_shared_for_region: Error invoking rpc at '.../rpc/issue/get_shared_for_region/': check error logs for more details#x1B[0m
tests.sentry.web.frontend.test_shared_group_details.SharedGroupDetailsTest::test_get_not_found
Stack Traces | 2.45s run time
#x1B[1m#x1B[.../web/frontend/test_shared_group_details.py#x1B[0m:45: in test_get_not_found
    response = self.client.get(".../share/issue/lolnope/", HTTP_HOST=self.org_domain)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:1124: in get
    response = super().get(
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:475: in get
    return self.generic(
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:671: in generic
    return self.request(**r)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:1087: in request
    self.check_exception(response)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/test/client.py#x1B[0m:802: in check_exception
    raise exc_value
#x1B[1m#x1B[31m.venv/lib/python3.13.../core/handlers/exception.py#x1B[0m:55: in inner
    response = get_response(request)
#x1B[1m#x1B[31m.venv/lib/python3.13.../core/handlers/base.py#x1B[0m:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../views/generic/base.py#x1B[0m:104: in view
    return self.dispatch(request, *args, **kwargs)
#x1B[1m#x1B[.../sentry/silo/base.py#x1B[0m:158: in override
    return original_method(*args, **kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../django/utils/decorators.py#x1B[0m:48: in _wrapper
    return bound_method(*args, **kwargs)
#x1B[1m#x1B[31m.venv/lib/python3.13.../views/decorators/csrf.py#x1B[0m:65: in _view_wrapper
    return view_func(request, *args, **kwargs)
#x1B[1m#x1B[.../web/frontend/base.py#x1B[0m:417: in dispatch
    return self.handle(request, *args, **kwargs)
#x1B[1m#x1B[.../web/frontend/react_page.py#x1B[0m:207: in handle
    return self.handle_react(request, **kwargs)
#x1B[1m#x1B[.../web/frontend/react_page.py#x1B[0m:102: in handle_react
    for key, value in self.meta_tags(request, **kwargs).items()
#x1B[1m#x1B[.../web/frontend/shared_group_details.py#x1B[0m:18: in meta_tags
    group = issue_service.get_shared_for_org(slug=org_slug, share_id=share_id)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:354: in remote_method
    return dispatch_remote_call(
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:476: in dispatch_remote_call
    return remote_silo_call.dispatch(use_test_client)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:511: in dispatch
    serial_response = self._send_to_remote_silo(use_test_client)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:582: in _send_to_remote_silo
    self._raise_from_response_status_error(response)
#x1B[1m#x1B[.../hybridcloud/rpc/service.py#x1B[0m:605: in _raise_from_response_status_error
    raise self._remote_exception(
#x1B[1m#x1B[31mE   sentry.hybridcloud.rpc.service.RpcRemoteException: issue.get_shared_for_org: Error invoking rpc at '.../rpc/issue/get_shared_for_org/': check error logs for more details#x1B[0m

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Contributor

@Christinarlong Christinarlong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the steps make sense to me, some terminology questions though for the PR descr.

  1. I assume context pane refer to the sidebar on the Jira side?
  2. Is the Jira parser an endpoint? What is that referring to? Is that a Jira side thing?

@@ -45,3 +46,25 @@ def upsert_issue_email_reply(
# Call the task synchronously so that the outbox retry works
# correctly should this fail.
process_inbound_email(from_email, group_id, text)

def get_linked_issues(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be better to specify this naming/function is specifically for integrations (?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let me update that. Originally it didn't have integrations in the query, but turns out it was needed because external issue keys aren't unique.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Scope: Backend Automatically applied to PRs that change backend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants