From b278a892ebbacd615ffa03d4f6b2315b7479b914 Mon Sep 17 00:00:00 2001 From: Andrey Druz Date: Sat, 17 Jan 2026 22:50:15 +0200 Subject: [PATCH 1/2] feat: Enhance GitLab integration by ensuring all issue and PR labels exist in project labels - Added a method to ensure that all labels from issues and PRs are included in the project labels array, addressing potential frontend errors due to missing group-level labels. - Updated avatar fetching methods to handle authentication for private GitLab instances, improving reliability in retrieving user and project avatars. --- lib/Reference/GitlabReferenceProvider.php | 32 +++++++++++++++++++++ lib/Service/GitlabAPIService.php | 34 +++++++++++++++++++++-- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/lib/Reference/GitlabReferenceProvider.php b/lib/Reference/GitlabReferenceProvider.php index eb6e3bc..ed425e7 100644 --- a/lib/Reference/GitlabReferenceProvider.php +++ b/lib/Reference/GitlabReferenceProvider.php @@ -153,6 +153,8 @@ public function resolveReference(string $referenceText): ?IReference { $projectLabels = $account !== null ? $this->gitlabAPIService->getProjectLabels($account, $baseUrl, $projectInfo['id']) : []; $commentInfo = $this->getIssueCommentInfo($account, $baseUrl, $projectInfo['id'], $issueId, $end); $issueInfo = $this->gitlabAPIService->getIssueInfo($account, $baseUrl, $projectInfo['id'], $issueId); + // Ensure all issue labels are in projectLabels (some may be group-level labels not returned by project labels API) + $projectLabels = $this->ensureLabelsExist($projectLabels, $issueInfo['labels'] ?? []); $reference = new Reference($referenceText); $reference->setRichObject( Application::APP_ID, @@ -183,6 +185,8 @@ public function resolveReference(string $referenceText): ?IReference { $projectLabels = $account !== null ? $this->gitlabAPIService->getProjectLabels($account, $baseUrl, $projectInfo['id']) : []; $commentInfo = $this->getPrCommentInfo($account, $baseUrl, $projectInfo['id'], $prId, $end); $prInfo = $this->gitlabAPIService->getPrInfo($account, $baseUrl, $projectInfo['id'], $prId); + // Ensure all PR labels are in projectLabels (some may be group-level labels not returned by project labels API) + $projectLabels = $this->ensureLabelsExist($projectLabels, $prInfo['labels'] ?? []); $reference = new Reference($referenceText); $reference->setRichObject( Application::APP_ID, @@ -319,6 +323,34 @@ private function getGenericPrInfo(array $prInfo, array $projectLabels): array { return $info; } + /** + * Ensure all labels from the issue/PR exist in the project labels array. + * This is needed because some labels may be group-level labels that are not returned + * by the project labels API, causing frontend errors when trying to find them. + * + * @param array $projectLabels + * @param array $itemLabels + * @return array + */ + private function ensureLabelsExist(array $projectLabels, array $itemLabels): array { + $existingLabelNames = array_map(static fn (array $label) => $label['name'], $projectLabels); + + foreach ($itemLabels as $labelName) { + if (!in_array($labelName, $existingLabelNames, true)) { + // Add a minimal label entry for labels not found in project labels + $projectLabels[] = [ + 'id' => null, + 'name' => $labelName, + 'color' => '#808080', + 'text_color' => '#FFFFFF', + 'description' => null, + ]; + } + } + + return $projectLabels; + } + /** * @param string $gitlabUrl * @param string $url diff --git a/lib/Service/GitlabAPIService.php b/lib/Service/GitlabAPIService.php index d72287f..5b42fbe 100644 --- a/lib/Service/GitlabAPIService.php +++ b/lib/Service/GitlabAPIService.php @@ -214,7 +214,10 @@ public function getGroupsList(GitLabAccount $account): array { public function getUserAvatar(GitlabAccount $account, string $baseUrl, int $gitlabUserId): array { $userInfo = $this->request($account, $baseUrl, 'users/' . $gitlabUserId); if (!isset($userInfo['error']) && isset($userInfo['avatar_url'])) { - return ['avatarContent' => $this->client->get($userInfo['avatar_url'])->getBody()]; + $avatarContent = $this->fetchAvatarContent($account, $userInfo['avatar_url']); + if ($avatarContent !== null) { + return ['avatarContent' => $avatarContent]; + } } return ['userInfo' => $userInfo]; } @@ -222,11 +225,38 @@ public function getUserAvatar(GitlabAccount $account, string $baseUrl, int $gitl public function getProjectAvatar(GitlabAccount $account, string $baseUrl, int $projectId): array { $projectInfo = $this->request($account, $baseUrl, 'projects/' . $projectId); if (!isset($projectInfo['error']) && isset($projectInfo['avatar_url'])) { - return ['avatarContent' => $this->client->get($projectInfo['avatar_url'])->getBody()]; + $avatarContent = $this->fetchAvatarContent($account, $projectInfo['avatar_url']); + if ($avatarContent !== null) { + return ['avatarContent' => $avatarContent]; + } } return ['projectInfo' => $projectInfo]; } + /** + * Fetch avatar content with authentication if needed (for private GitLab instances) + */ + private function fetchAvatarContent(GitlabAccount $account, string $avatarUrl): ?string { + $options = [ + 'headers' => [ + 'User-Agent' => 'Nextcloud GitLab integration', + ], + ]; + + // Add authentication for private GitLab instances + $accessToken = $account->getClearToken(); + if ($accessToken !== '') { + $options['headers']['Authorization'] = 'Bearer ' . $accessToken; + } + + try { + return $this->client->get($avatarUrl, $options)->getBody(); + } catch (ClientException $e) { + $this->logger->warning('Failed to fetch GitLab avatar: ' . $e->getMessage(), ['app' => Application::APP_ID]); + return null; + } + } + public function getProjectInfo(?GitlabAccount $account, string $baseUrl, string $owner, string $repo): array { return $this->request($account, $baseUrl, 'projects/' . urlencode($owner . '/' . $repo)); } From 1d00c16c77c955ac8ae58dc2674e1092fb1cd03b Mon Sep 17 00:00:00 2001 From: Andrey Druz Date: Sat, 17 Jan 2026 23:05:33 +0200 Subject: [PATCH 2/2] refactor: Simplify avatar fetching in GitLabAPIService - Replaced the custom avatar fetching method with direct HTTP client calls for user and project avatars, improving code clarity and reducing complexity. - Added error handling to log failures when fetching avatars, enhancing debugging capabilities. --- lib/Service/GitlabAPIService.php | 38 +++++++------------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/lib/Service/GitlabAPIService.php b/lib/Service/GitlabAPIService.php index 5b42fbe..af96d4a 100644 --- a/lib/Service/GitlabAPIService.php +++ b/lib/Service/GitlabAPIService.php @@ -214,9 +214,10 @@ public function getGroupsList(GitLabAccount $account): array { public function getUserAvatar(GitlabAccount $account, string $baseUrl, int $gitlabUserId): array { $userInfo = $this->request($account, $baseUrl, 'users/' . $gitlabUserId); if (!isset($userInfo['error']) && isset($userInfo['avatar_url'])) { - $avatarContent = $this->fetchAvatarContent($account, $userInfo['avatar_url']); - if ($avatarContent !== null) { - return ['avatarContent' => $avatarContent]; + try { + return ['avatarContent' => $this->client->get($userInfo['avatar_url'])->getBody()]; + } catch (Exception $e) { + $this->logger->debug('Failed to fetch GitLab user avatar: ' . $e->getMessage(), ['app' => Application::APP_ID]); } } return ['userInfo' => $userInfo]; @@ -225,38 +226,15 @@ public function getUserAvatar(GitlabAccount $account, string $baseUrl, int $gitl public function getProjectAvatar(GitlabAccount $account, string $baseUrl, int $projectId): array { $projectInfo = $this->request($account, $baseUrl, 'projects/' . $projectId); if (!isset($projectInfo['error']) && isset($projectInfo['avatar_url'])) { - $avatarContent = $this->fetchAvatarContent($account, $projectInfo['avatar_url']); - if ($avatarContent !== null) { - return ['avatarContent' => $avatarContent]; + try { + return ['avatarContent' => $this->client->get($projectInfo['avatar_url'])->getBody()]; + } catch (Exception $e) { + $this->logger->debug('Failed to fetch GitLab project avatar: ' . $e->getMessage(), ['app' => Application::APP_ID]); } } return ['projectInfo' => $projectInfo]; } - /** - * Fetch avatar content with authentication if needed (for private GitLab instances) - */ - private function fetchAvatarContent(GitlabAccount $account, string $avatarUrl): ?string { - $options = [ - 'headers' => [ - 'User-Agent' => 'Nextcloud GitLab integration', - ], - ]; - - // Add authentication for private GitLab instances - $accessToken = $account->getClearToken(); - if ($accessToken !== '') { - $options['headers']['Authorization'] = 'Bearer ' . $accessToken; - } - - try { - return $this->client->get($avatarUrl, $options)->getBody(); - } catch (ClientException $e) { - $this->logger->warning('Failed to fetch GitLab avatar: ' . $e->getMessage(), ['app' => Application::APP_ID]); - return null; - } - } - public function getProjectInfo(?GitlabAccount $account, string $baseUrl, string $owner, string $repo): array { return $this->request($account, $baseUrl, 'projects/' . urlencode($owner . '/' . $repo)); }