fix(github): show GitHub App errors when loading repositories

Closes #10174

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras BacsaiandClaude Opus 5.5 committed 2026-10-27 15:46:26 +01:00
1 parent 086dbb8b1e
commit c3d110496c
3 files changed
+74 -17

No files matched your search

@@ -115,17 +115,22 @@ class GithubPrivateRepository extends Component
->where('is_public', false)
->whereNotNull('app_id')
->findOrFail($github_app_id);
$token = generateGithubInstallationToken($this->github_app);
$repositories = loadRepositoryByPage($this->github_app, $token, $this->page);
$this->total_repositories_count = $repositories['total_count'];
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
if ($this->repositories->count() < $this->total_repositories_count) {
try {
$token = generateGithubInstallationToken($this->github_app);
$repositories = loadRepositoryByPage($this->github_app, $token, $this->page);
$this->total_repositories_count = $repositories['total_count'];
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
while ($this->repositories->count() < $this->total_repositories_count) {
$this->page++;
$repositories = loadRepositoryByPage($this->github_app, $token, $this->page);
$this->total_repositories_count = $repositories['total_count'];
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
}
} catch (\Throwable $e) {
$this->repositories = collect();
handleError($e, $this);
return;
}
$this->repositories = $this->repositories->sortBy('name');
if ($this->repositories->count() > 0) {
@@ -141,12 +146,17 @@ class GithubPrivateRepository extends Component
$this->selected_repository_repo = data_get($repository, 'name');
$this->branches = collect();
$this->page = 1;
$this->loadBranchByPage();
if ($this->total_branches_count === 100) {
while ($this->total_branches_count === 100) {
$this->page++;
try {
do {
$this->loadBranchByPage();
}
$this->page++;
} while ($this->total_branches_count === 100);
} catch (\Throwable $e) {
$this->branches = collect();
$this->total_branches_count = 0;
handleError($e, $this);
return;
}
$this->branches = sortBranchesByPriority($this->branches);
$defaultBranch = data_get($repository, 'default_branch', 'main');
@@ -157,7 +167,7 @@ class GithubPrivateRepository extends Component
$this->detectRepository();
}
protected function loadBranchByPage()
protected function loadBranchByPage(): void
{
$token = generateGithubInstallationToken($this->github_app);
@@ -170,7 +180,9 @@ class GithubPrivateRepository extends Component
]);
$json = $response->json();
if ($response->status() !== 200) {
return $this->dispatch('error', $json['message']);
$error = data_get($json, 'message', "HTTP {$response->status()}");
throw new \RuntimeException("Failed to load branches of {$this->selected_repository_owner}/{$this->selected_repository_repo} with error: ".$error);
}
$this->total_branches_count = count($json);
+4 -5
View File
@@ -212,7 +212,7 @@ function generateGithubToken(GithubApp $source, string $type)
if (! $response->successful()) {
$error = data_get($response->json(), 'message', "HTTP {$response->status()}");
if ($error === 'Not Found') {
$error = 'Repository not found. Is it moved or deleted?';
$error = "GitHub App installation {$source->installation_id} was not found. Install the GitHub App again or update its installation ID.";
}
throw new RuntimeException("Failed to get installation token for {$source->name} with error: ".$error);
}
@@ -388,10 +388,9 @@ function loadRepositoryByPage(GithubApp $source, string $token, int $page)
]);
$json = $response->json();
if ($response->status() !== 200) {
return [
'total_count' => 0,
'repositories' => [],
];
$error = data_get($json, 'message', "HTTP {$response->status()}");
throw new RuntimeException("Failed to load repositories for {$source->name} with error: ".$error);
}
if ($json['total_count'] === 0) {
@@ -344,6 +344,52 @@ describe('GitHub Private Repository Component', function () {
->assertSee('Refresh');
});
test('loadRepositories shows an error when the installation is not found', function () {
Http::fake([
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
'Date' => now()->toRfc7231String(),
]),
'https://api.github.com/app/installations/67890/access_tokens' => Http::response(['message' => 'Not Found'], 404),
]);
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->call('loadRepositories', $this->githubApp->id)
->assertDispatched('error', fn (string $event, array $params) => str_contains($params[0], 'installation 67890 was not found'))
->assertSet('current_step', 'github_apps');
});
test('loadRepositories shows the GitHub error when listing repositories fails', function () {
Http::fake([
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
'Date' => now()->toRfc7231String(),
]),
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
'token' => 'fake-installation-token',
], 201),
'https://api.github.com/installation/repositories*' => Http::response(['message' => 'Resource not accessible by integration'], 403),
]);
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->call('loadRepositories', $this->githubApp->id)
->assertDispatched('error', fn (string $event, array $params) => str_contains($params[0], 'Resource not accessible by integration'))
->assertSet('current_step', 'github_apps');
});
test('loadBranches stops and shows the GitHub error when a later branch page fails', function () {
$repository = ['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']];
fakeGithubHttp([$repository]);
Http::fake([
'https://api.github.com/repos/testuser/alpha-repo/branches*' => Http::sequence()
->push(collect(range(1, 100))->map(fn (int $i) => ['name' => "branch-{$i}"])->all(), 200)
->whenEmpty(Http::response(['message' => 'API rate limit exceeded'], 403)),
]);
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->call('loadRepositories', $this->githubApp->id)
->call('loadBranches')
->assertDispatched('error', fn (string $event, array $params) => str_contains($params[0], 'API rate limit exceeded'));
});
test('refresh button is not visible before repositories are loaded', function () {
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->assertDontSee('Refresh');