diff --git a/app/Actions/Git/Contracts/GitProviderClient.php b/app/Actions/Git/Contracts/GitProviderClient.php index 297ff8e..947c510 100644 --- a/app/Actions/Git/Contracts/GitProviderClient.php +++ b/app/Actions/Git/Contracts/GitProviderClient.php @@ -3,6 +3,7 @@ namespace App\Actions\Git\Contracts; use App\Actions\Git\Data\GitAuthenticatedUser; +use App\Actions\Git\Data\GitBranch; use App\Actions\Git\Data\GitRepository; use App\Actions\Git\GitProviderKey; @@ -16,4 +17,14 @@ interface GitProviderClient * @return array */ public function listRepositories(string $baseUrl, string $token): array; + + /** + * @return array + */ + public function listBranches( + string $baseUrl, + string $token, + string $owner, + string $repository, + ): array; } diff --git a/app/Actions/Git/Data/GitBranch.php b/app/Actions/Git/Data/GitBranch.php new file mode 100644 index 0000000..e187bb6 --- /dev/null +++ b/app/Actions/Git/Data/GitBranch.php @@ -0,0 +1,13 @@ + + */ + public function listBranches( + string $baseUrl, + string $token, + string $owner, + string $repository, + ): array { + $page = 1; + $branches = []; + + do { + /** @var array $payload */ + $payload = $this->request($baseUrl, $token) + ->get( + '/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/branches', + [ + 'limit' => 100, + 'page' => $page, + ], + ) + ->throw() + ->json(); + + foreach ($payload as $branch) { + $branches[] = new GitBranch( + name: $branch['name'], + ); + } + + $page++; + } while (count($payload) === 100); + + return $branches; + } + private function request(string $baseUrl, string $token): PendingRequest { return Http::acceptJson() diff --git a/app/Actions/Git/ResolveTicketBranchMatches.php b/app/Actions/Git/ResolveTicketBranchMatches.php new file mode 100644 index 0000000..dd8c8c4 --- /dev/null +++ b/app/Actions/Git/ResolveTicketBranchMatches.php @@ -0,0 +1,151 @@ +, + * error: string|null, + * repository_connection: array{ + * base_url: string, + * created_at: string|null, + * has_personal_access: bool, + * id: int, + * owner_name: string, + * project_id: int, + * provider: string, + * provider_label: string, + * provider_repository_id: string, + * repository_full_name: string, + * repository_name: string, + * repository_url: string, + * updated_at: string|null + * } + * }> + */ + public function handle(User $user, Project $project, Ticket $ticket): array + { + /** @var Collection $repositoryConnections */ + $repositoryConnections = $project->repositoryConnections() + ->get(); + + return $repositoryConnections + ->map(function (RepositoryConnection $repositoryConnection) use ($ticket, $user): array { + $userGitConnection = $this->matchingUserGitConnection($user, $repositoryConnection); + $hasPersonalAccess = $userGitConnection !== null; + + if ($userGitConnection === null) { + return [ + 'branches' => [], + 'error' => null, + 'repository_connection' => $this->repositoryConnectionPayload( + $repositoryConnection, + false, + ), + ]; + } + + try { + $branches = collect( + $this->gitProviderManager + ->client($userGitConnection->provider) + ->listBranches( + $userGitConnection->base_url, + $userGitConnection->api_token, + $repositoryConnection->owner_name, + $repositoryConnection->repository_name, + ), + ) + ->filter(fn (GitBranch $branch): bool => $this->ticketKeyReferenceMatcher->matches( + $ticket->ticket_key, + $branch->name, + )) + ->map(fn (GitBranch $branch): array => [ + 'name' => $branch->name, + ]) + ->values() + ->all(); + + $error = null; + } catch (Throwable) { + $branches = []; + $error = 'KanbanGit could not load branches from this repository right now.'; + } + + return [ + 'branches' => $branches, + 'error' => $error, + 'repository_connection' => $this->repositoryConnectionPayload( + $repositoryConnection, + $hasPersonalAccess, + ), + ]; + }) + ->values() + ->all(); + } + + /** + * @return array{ + * base_url: string, + * created_at: string|null, + * has_personal_access: bool, + * id: int, + * owner_name: string, + * project_id: int, + * provider: string, + * provider_label: string, + * provider_repository_id: string, + * repository_full_name: string, + * repository_name: string, + * repository_url: string, + * updated_at: string|null + * } + */ + private function repositoryConnectionPayload( + RepositoryConnection $repositoryConnection, + bool $hasPersonalAccess, + ): array { + return [ + 'base_url' => $repositoryConnection->base_url, + 'created_at' => $repositoryConnection->created_at?->toISOString(), + 'has_personal_access' => $hasPersonalAccess, + 'id' => $repositoryConnection->id, + 'owner_name' => $repositoryConnection->owner_name, + 'project_id' => $repositoryConnection->project_id, + 'provider' => $repositoryConnection->provider->value, + 'provider_label' => $repositoryConnection->provider->label(), + 'provider_repository_id' => $repositoryConnection->provider_repository_id, + 'repository_full_name' => $repositoryConnection->repository_full_name, + 'repository_name' => $repositoryConnection->repository_name, + 'repository_url' => $repositoryConnection->repository_url, + 'updated_at' => $repositoryConnection->updated_at?->toISOString(), + ]; + } + + private function matchingUserGitConnection( + User $user, + RepositoryConnection $repositoryConnection, + ): ?UserGitConnection { + return $user->gitConnections() + ->where('provider', $repositoryConnection->provider) + ->where('base_url', $repositoryConnection->base_url) + ->first(); + } +} diff --git a/app/Actions/Git/TicketKeyReferenceMatcher.php b/app/Actions/Git/TicketKeyReferenceMatcher.php new file mode 100644 index 0000000..0966af5 --- /dev/null +++ b/app/Actions/Git/TicketKeyReferenceMatcher.php @@ -0,0 +1,14 @@ + $parentTicket->title, ]), 'project' => $project, + 'repositoryBranches' => $this->resolveTicketBranchMatches->handle( + $request->user(), + $project, + $ticket, + ), 'status' => session('status'), 'ticket' => $ticket->load([ 'fieldValues.ticketFieldDefinition', diff --git a/docs/missing-features.md b/docs/missing-features.md index ab39a07..2bd1b42 100644 --- a/docs/missing-features.md +++ b/docs/missing-features.md @@ -66,4 +66,6 @@ - later: - if deleting ticket type select a new ticket type which all existing ticket should get or select delete all tickets of that type - - repository_connections check base Url maybe unnessecary? \ No newline at end of file + - repository_connections check base Url maybe unnessecary? + - Git listBranches replace baseUrl, owner, rperoName with GItReprository + - replace Kanbangit with env App Name \ No newline at end of file diff --git a/resources/js/pages/projects/Ticket.vue b/resources/js/pages/projects/Ticket.vue index bee5c84..af4faf0 100644 --- a/resources/js/pages/projects/Ticket.vue +++ b/resources/js/pages/projects/Ticket.vue @@ -1,5 +1,6 @@