From 9647ceeb842ac7708c5f40ad6edb8f41624528b5 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 23:27:02 +0000 Subject: [PATCH] fix(jira): use paginated project endpoint in get_organization_config The settings page called get_projects_list() which hits the deprecated unpaginated GET /rest/api/2/project endpoint, returning every Jira project in a single response. For large enterprise instances this causes timeouts, leaving the configuration page with an infinite spinner. Switch to get_projects_paginated(maxResults=50) so the initial settings-page render is bounded. Projects beyond the first 50 are accessible via the existing typeahead search endpoint (JiraSearchEndpoint), which already uses the paginated API. Fixes #95257 Co-authored-by: Nick Meisenheimer --- src/sentry/integrations/jira/integration.py | 7 ++- .../integrations/jira/test_integration.py | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/sentry/integrations/jira/integration.py b/src/sentry/integrations/jira/integration.py index 7ff28cf8592f..8d4f965253ef 100644 --- a/src/sentry/integrations/jira/integration.py +++ b/src/sentry/integrations/jira/integration.py @@ -167,9 +167,14 @@ def get_organization_config(self) -> list[dict[str, Any]]: client = self.get_client() try: + # Use the paginated endpoint to avoid fetching all projects at once, + # which can time out for large Jira instances. The settings page + # dropdown search (typeahead) handles finding projects beyond this + # initial page via JiraSearchEndpoint. + projects_response = client.get_projects_paginated(params={"maxResults": 50}) projects: list[JiraProjectMapping] = [ JiraProjectMapping(value=p["id"], label=p["name"]) - for p in client.get_projects_list() + for p in projects_response.get("values", []) ] self._set_status_choices_in_organization_config(configuration, projects) configuration[0]["addDropdown"]["items"] = projects diff --git a/tests/sentry/integrations/jira/test_integration.py b/tests/sentry/integrations/jira/test_integration.py index 6ed2a9e2dc9c..d84f6b050b8e 100644 --- a/tests/sentry/integrations/jira/test_integration.py +++ b/tests/sentry/integrations/jira/test_integration.py @@ -1197,6 +1197,60 @@ def test_update_organization_config_issues_keys(self) -> None: "moon", ] + @responses.activate + def test_get_organization_config_uses_paginated_endpoint(self) -> None: + """Ensure the settings-page config load uses the bounded /project/search + endpoint, not the deprecated unpaginated /project endpoint that times out + for large Jira instances.""" + integration = self.create_provider_integration( + provider="jira", + name="Example Jira", + metadata={ + "oauth_client_id": "oauth-client-id", + "shared_secret": "a-super-secret-key-from-atlassian", + "base_url": "https://example.atlassian.net", + "domain_name": "example.atlassian.net", + }, + ) + integration.add_organization(self.organization, self.user) + + responses.add( + responses.GET, + "https://example.atlassian.net/rest/api/2/project/search", + json={ + "values": [ + {"id": "10000", "name": "Alpha", "key": "ALP"}, + {"id": "10001", "name": "Beta", "key": "BET"}, + ], + "total": 2, + "isLast": True, + }, + ) + responses.add( + responses.GET, + "https://example.atlassian.net/rest/api/2/status", + json=[{"id": "1", "name": "To Do"}, {"id": "2", "name": "Done"}], + ) + + installation = integration.get_installation(self.organization.id) + config = installation.get_organization_config() + + # Confirm the paginated endpoint was hit (not the legacy /project endpoint) + called_urls = [call.request.url for call in responses.calls] + assert any("/rest/api/2/project/search" in url for url in called_urls), ( + "Expected paginated endpoint /project/search to be called" + ) + assert not any( + url.endswith("/rest/api/2/project") or "/rest/api/2/project?" in url + for url in called_urls + ), "Unexpected call to deprecated unpaginated /project endpoint" + + # addDropdown items should be populated from the paginated response + add_dropdown_items = config[0]["addDropdown"]["items"] + assert len(add_dropdown_items) == 2 + assert add_dropdown_items[0]["value"] == "10000" + assert add_dropdown_items[0]["label"] == "Alpha" + @responses.activate def test_get_config_data(self) -> None: integration = self.create_provider_integration(