From b142483a7c3aa72e7838b97c8fbe53e8314ed143 Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Tue, 26 May 2026 08:37:59 +0000 Subject: [PATCH] fix: migrate Jira Cloud search to /rest/api/3/search/jql Atlassian removed the legacy /rest/api/{2,3}/search endpoint in late 2025 (CHANGE-2046); Jira Cloud users have been getting 410 Gone on every notification cron and unified-search call since the shutdown. Switch the two Cloud code paths to /rest/api/3/search/jql: - search(): add fields=*all to the existing JQL/params; endpoint swap. - getNotifications(): the new endpoint 400s on empty JQL, so pass jql='assignee = currentUser()' explicitly. The PHP-side assignee filter stays as a redundant safety net. Net result set is unchanged for the cron's purposes. Self-hosted Jira (Server / Data Center) is unaffected by CHANGE-2046 and keeps using /rest/api/2/search. Fixes #122 Signed-off-by: Oleksander Piskun --- CHANGELOG.md | 1 + lib/Service/JiraAPIService.php | 14 +++++++++++--- tests/unit/Service/JiraAPIServiceTest.php | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 238ad88..88633b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Stop spamming the log with `\InvalidArgumentException` deprecation warnings from the notifier; throw `OCP\Notification\UnknownNotificationException` instead for unknown notifications. - Fix `ValueError` from `vsprintf()` that broke notifications for non-English users. +- Migrate Jira Cloud search and notification calls to `/rest/api/3/search/jql`; the legacy `/rest/api/2/search` endpoint was removed by Atlassian (CHANGE-2046) and Cloud users were getting `410 Gone`. Self-hosted Jira (Server / Data Center) is unaffected and keeps the v2 endpoint (#122). ## 1.4.1 - 2025-11-10 diff --git a/lib/Service/JiraAPIService.php b/lib/Service/JiraAPIService.php index ecc4fe1..2d861c4 100644 --- a/lib/Service/JiraAPIService.php +++ b/lib/Service/JiraAPIService.php @@ -169,13 +169,17 @@ public function getNotifications(string $userId, ?string $since = null, ?int $li } } } else { - // Jira cloud + // Jira cloud — /rest/api/2/search was removed (CHANGE-2046), + // must use the new /rest/api/3/search/jql endpoint which requires + // an explicit JQL and an explicit fields list. + $endPoint = 'rest/api/3/search/jql'; + $params = ['jql' => 'assignee = currentUser()', 'fields' => '*all']; $resources = $this->getJiraResources($userId); foreach ($resources as $resource) { $cloudId = $resource['id']; $jiraUrl = $resource['url']; - $issuesResult = $this->networkService->oauthRequest($userId, 'ex/jira/' . $cloudId . '/' . $endPoint); + $issuesResult = $this->networkService->oauthRequest($userId, 'ex/jira/' . $cloudId . '/' . $endPoint, $params); if (!isset($issuesResult['error']) && isset($issuesResult['issues'])) { foreach ($issuesResult['issues'] as $k => $issue) { $issuesResult['issues'][$k]['jiraUrl'] = $jiraUrl; @@ -324,7 +328,11 @@ public function search(string $userId, string $query, int $offset = 0, int $limi $myIssues[] = $issuesResult['issues'][$k]; } } else { - // Jira cloud + // Jira cloud — /rest/api/2/search was removed (CHANGE-2046), + // must use the new /rest/api/3/search/jql endpoint which requires + // an explicit fields list. + $endPoint = 'rest/api/3/search/jql'; + $params['fields'] = '*all'; $resources = $this->getJiraResources($userId); foreach ($resources as $resource) { diff --git a/tests/unit/Service/JiraAPIServiceTest.php b/tests/unit/Service/JiraAPIServiceTest.php index d2574c5..bf57668 100644 --- a/tests/unit/Service/JiraAPIServiceTest.php +++ b/tests/unit/Service/JiraAPIServiceTest.php @@ -63,7 +63,8 @@ public function testSearch() { $this->networkService->method('oauthRequest')->willReturnCallback(function ( string $userId, string $endPoint, array $params = [], string $method = 'GET', ) { - if (str_contains($endPoint, 'rest/api/2/search')) { + if (str_contains($endPoint, 'rest/api/3/search/jql')) { + $this->assertSame('*all', $params['fields'] ?? null); return json_decode(file_get_contents('tests/data/search.json'), true); } return 'dummy';