feat(KAN-44): rebuild project repository settings
CI/CD / preconditions (pull_request) Successful in 1s
CI/CD / test_and_check_app (pull_request) Canceled after 0s
CI/CD / Publish immutable image (pull_request) Canceled after 0s

This commit is contained in:
2026-09-16 09:12:39 +02:00
parent b226ee8d48
commit fe4eed227e
3 changed files with 1188 additions and 618 deletions
+77
View File
@@ -583,6 +583,8 @@ export const en = {
'profile.delete.confirmAction': 'Permanently delete account',
'common.cancel': 'Cancel',
'common.close': 'Close',
'common.edit': 'Edit',
'common.remove': 'Remove',
'common.retry': 'Try again',
'feedback.empty': 'Nothing here yet',
'feedback.saved': 'Changes saved',
@@ -874,6 +876,81 @@ export const en = {
'inheritance.area.ticketLinkTypes': 'Ticket link types',
'inheritance.area.repositoryConnections': 'Repository connections',
'inheritance.area.notificationActions': 'Notification actions',
'repositorySettings.pageTitle': '{project} repository settings',
'repositorySettings.title': 'Project repositories',
'repositorySettings.description':
'Choose the repositories this project uses for ticket branches and pull requests.',
'repositorySettings.projectScope':
'Repository links belong to the project. Every team member keeps using the same linked repositories, while access is granted through their own personal Git connections.',
'repositorySettings.linkedRepositories': 'Linked repositories',
'repositorySettings.linkAction': 'Link repository',
'repositorySettings.empty': 'No project repositories yet',
'repositorySettings.emptyDescription':
'Link a repository from one of your personal Git connections to make it available to project tickets.',
'repositorySettings.personalAccessReady': 'Personal access ready',
'repositorySettings.personalAccessMissing': 'Personal access needed',
'repositorySettings.ticketUsage':
'Tickets use this project link to discover matching branches and pull requests.',
'repositorySettings.openRepository':
'Open {repository} in the Git provider (opens in a new tab)',
'repositorySettings.open': 'Open repository',
'repositorySettings.connectAccess': 'Connect Git access',
'repositorySettings.editRepository':
'Change project repository {repository}',
'repositorySettings.removeRepository':
'Remove project repository {repository}',
'repositorySettings.personalSources': 'Personal Git access',
'repositorySettings.personalSourcesDescription':
'These account connections are private to you and are only used to discover repositories you can link to this project.',
'repositorySettings.manageConnections': 'Manage Git connections',
'repositorySettings.refresh': 'Refresh repositories',
'repositorySettings.refreshing': 'Loading repositories…',
'repositorySettings.refreshingDescription':
'The latest repositories are being requested from your Git providers.',
'repositorySettings.noConnections': 'No personal Git connection',
'repositorySettings.noConnectionsDescription':
'Add a personal Git connection before linking or changing a project repository.',
'repositorySettings.detectedSources': 'Detected personal sources',
'repositorySettings.unavailable': 'Provider unavailable',
'repositorySettings.repositoryCount': '{count} repositories',
'repositorySettings.linkTitle': 'Link a project repository',
'repositorySettings.linkDescription':
'Choose a personal connection, find a repository, and add it to this project.',
'repositorySettings.editTitle': 'Change project repository',
'repositorySettings.editDescription':
'Replace this project link with another repository. Tickets will use the updated link after saving.',
'repositorySettings.removeTitle': 'Remove repository',
'repositorySettings.removeDescription':
'Remove this repository from the project? Tickets will no longer discover its branches or pull requests.',
'repositorySettings.linked': 'Repository linked to the project.',
'repositorySettings.updated': 'Project repository updated.',
'repositorySettings.removed': 'Repository removed from the project.',
'repositorySettings.matchingConnectionMissing':
'Matching personal access is missing',
'repositorySettings.matchingConnectionMissingDescription':
'The project link remains available to the team, but this account cannot query it until you add a matching provider connection. You can still choose a different accessible repository below.',
'repositorySettings.personalConnection': 'Personal Git connection',
'repositorySettings.personalConnectionHelp':
'Used for discovery only; it is not shared with the project.',
'repositorySettings.providerError':
'Repositories could not be loaded from this provider',
'repositorySettings.searchLabel': 'Find a repository',
'repositorySettings.searchPlaceholder':
'Search by repository, owner, or namespace',
'repositorySettings.noRepositories': 'No repositories available',
'repositorySettings.noRepositoriesDescription':
'This connection is working, but the provider returned no repositories you can access.',
'repositorySettings.noSearchResults': 'No matching repositories',
'repositorySettings.noSearchResultsDescription':
'No repository matches “{search}”. Try a different name or clear the search.',
'repositorySettings.repositoryLabel': 'Available repositories',
'repositorySettings.forbidden': 'Project access denied',
'repositorySettings.forbiddenDescription':
'You no longer have permission to manage repositories for this project. Close this dialog and refresh the page.',
'repositorySettings.linking': 'Linking repository…',
'repositorySettings.saving': 'Saving repository…',
'repositorySettings.save': 'Save repository',
'repositorySettings.removing': 'Removing repository…',
'gitConnections.description':
'Connect your personal Git account to browse repositories, branches, and pull requests you can access.',
'gitConnections.privacy':
File diff suppressed because it is too large Load Diff
@@ -63,6 +63,58 @@ class ProjectRepositorySettingsControllerTest extends TestCase
);
}
/**
* Ensure repository discovery distinguishes empty provider results from provider failures.
*/
public function test_repository_settings_expose_personal_access_and_provider_states(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$emptyGitConnection = UserGitConnection::factory()->for($user)->create([
'account_login' => 'empty-account',
'base_url' => 'https://empty.git.test',
'provider' => GitProviderKey::Gitea,
]);
UserGitConnection::factory()->for($user)->create([
'account_login' => 'broken-account',
'base_url' => 'https://broken.git.test',
'provider' => GitProviderKey::Gitea,
]);
RepositoryConnection::factory()->for($project)->create([
'base_url' => $emptyGitConnection->base_url,
'provider' => GitProviderKey::Gitea,
]);
$this->syncProjectPermissions($project, $user, ['edit']);
Http::preventStrayRequests();
Http::fake([
'https://empty.git.test/api/v1/user/repos*' => Http::response([]),
'https://broken.git.test/api/v1/user/repos*' => Http::response([], 502),
]);
$this->actingAs($user)
->get(route('projects.settings.repositories', $project))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('projects/settings/Repositories')
->has('availableRepositorySources', 2)
->where('availableRepositorySources', function ($sources): bool {
$sourcesByAccount = collect($sources)->keyBy('account_login');
$emptySource = $sourcesByAccount->get('empty-account');
$brokenSource = $sourcesByAccount->get('broken-account');
return data_get($emptySource, 'provider') === GitProviderKey::Gitea->value
&& data_get($emptySource, 'provider_label') === GitProviderKey::Gitea->label()
&& data_get($emptySource, 'repositories') === []
&& data_get($emptySource, 'error') === null
&& data_get($brokenSource, 'repositories') === []
&& data_get($brokenSource, 'error') === config('app.name').' could not load repositories from this Git connection right now.';
})
->where('repositoryConnections.0.has_personal_access', true),
);
}
/**
* Ensure users without edit permission cannot manage repository settings.
*/