Skip to content

Core: PullRequestQuery builder with fluent filtering API #20

@jordanpartridge

Description

@jordanpartridge

Summary

Implement the core PullRequestQuery builder class with fluent filtering API for querying pull requests.

Acceptance Criteria

Query Builder Interface

  • Create Contracts\PullRequestQueryInterface with all query method signatures
  • Implement Services\PullRequestQuery implementing the interface
  • Support repository context initialization
  • Return strongly-typed collections of PR DTOs

Filtering Methods

// State filters
->whereOpen()
->whereClosed()
->whereMerged()
->whereDraft()
->whereState(string $state) // 'open' | 'closed' | 'all'

// Branch filters
->whereBase(string $branch)
->whereHead(string $branch)

// Author filters
->whereAuthor(string $username)

// Label filters
->whereLabel(string $label)
->whereLabels(array $labels) // Match any
->whereAllLabels(array $labels) // Match all

// Time filters
->whereStaleDays(int $days) // Updated more than X days ago
->whereSince(Carbon $date)
->whereUpdatedAfter(Carbon $date)
->whereCreatedAfter(Carbon $date)

// Review filters
->whereApproved()
->whereChangesRequested()
->whereReviewPending()

// Check filters
->whereChecksPassing()
->whereChecksFailing()

// Sort options
->orderByCreated(string $direction = 'desc')
->orderByUpdated(string $direction = 'desc')
->orderByPopularity() // comments count
->orderByLongRunning() // oldest updated first

Pagination

->perPage(int $count)
->page(int $page)

Execution Methods

->get(): Collection<PullRequest>
->first(): ?PullRequest
->count(): int
->exists(): bool
->pluck(string $key): Collection

Batch Operations

// Laravel collection proxying
->each(callable $callback)
->filter(callable $callback)
->map(callable $callback)

Technical Implementation

Class Structure

namespace ConduitUI\Pr\Services;

use ConduitUI\Connector\GitHub;
use ConduitUI\Pr\Contracts\PullRequestQueryInterface;
use ConduitUI\Pr\Data\PullRequest;
use Illuminate\Support\Collection;

final class PullRequestQuery implements PullRequestQueryInterface
{
    protected ?string $state = null;
    protected ?string $head = null;
    protected ?string $base = null;
    protected ?string $sort = 'created';
    protected string $direction = 'desc';
    protected int $perPage = 30;
    protected int $page = 1;
    
    public function __construct(
        protected GitHub $github,
        protected string $fullName,
    ) {}
    
    protected function buildEndpoint(): string
    {
        return "/repos/{$this->fullName}/pulls";
    }
    
    protected function buildParams(): array
    {
        // Build GitHub API query params
    }
    
    public function get(): Collection
    {
        $response = $this->github->get(
            $this->buildEndpoint(),
            $this->buildParams()
        );
        
        return collect($response->json())
            ->map(fn(array $pr) => PullRequest::fromArray($pr));
    }
}

Interface Definition

namespace ConduitUI\Pr\Contracts;

use Illuminate\Support\Collection;
use ConduitUI\Pr\Data\PullRequest;

interface PullRequestQueryInterface
{
    public function whereOpen(): self;
    public function whereClosed(): self;
    public function whereMerged(): self;
    public function whereState(string $state): self;
    public function whereBase(string $branch): self;
    public function whereHead(string $branch): self;
    public function whereAuthor(string $username): self;
    public function orderByCreated(string $direction = 'desc'): self;
    public function orderByUpdated(string $direction = 'desc'): self;
    public function perPage(int $count): self;
    public function page(int $page): self;
    public function get(): Collection;
    public function first(): ?PullRequest;
    public function count(): int;
}

Dependencies

  • conduit-ui/connector (GitHub API client)
  • Data\PullRequest DTO (created in separate issue)

Testing Requirements

it('filters open PRs')
    ->expect(fn() => PullRequests::forRepo('test/repo')->whereOpen()->get())
    ->toBeInstanceOf(Collection::class);

it('filters by base branch')
    ->expect(fn() => PullRequests::forRepo('test/repo')->whereBase('main')->get())
    ->toBeInstanceOf(Collection::class);

it('chains multiple filters')
    ->expect(fn() => 
        PullRequests::forRepo('test/repo')
            ->whereOpen()
            ->whereAuthor('jordan')
            ->whereBase('main')
            ->get()
    )->toBeInstanceOf(Collection::class);

References

Labels

  • enhancement
  • core
  • query-builder

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions