Summary
Create strongly-typed PullRequest data transfer object with chainable action methods for individual PR operations.
Acceptance Criteria
DTO Properties
namespace ConduitUI\Pr\Data;
use Carbon\Carbon;
use Illuminate\Support\Collection;
final readonly class PullRequest
{
public function __construct(
public int $id,
public int $number,
public string $title,
public string $body,
public string $state, // 'open' | 'closed'
public bool $draft,
public bool $merged,
public ?Carbon $mergedAt,
public User $author,
public string $headBranch,
public string $headRef,
public string $headSha,
public string $baseBranch,
public string $baseRef,
public string $baseSha,
public bool $mergeable,
public ?string $mergeableState,
public int $comments,
public int $reviewComments,
public int $commits,
public int $additions,
public int $deletions,
public int $changedFiles,
public Collection $labels, // Collection<Label>
public Collection $assignees, // Collection<User>
public Collection $requestedReviewers, // Collection<User>
public string $htmlUrl,
public string $diffUrl,
public string $patchUrl,
public Carbon $createdAt,
public Carbon $updatedAt,
public ?Carbon $closedAt,
) {}
public static function fromArray(array $data): self;
}
Chainable Action Methods
The DTO should return action builder instances for chainable operations:
// State changes
public function close(string $comment = null): PullRequestActions;
public function reopen(): PullRequestActions;
public function markDraft(): PullRequestActions;
public function markReady(): PullRequestActions;
// Reviews
public function approve(string $comment = null): ReviewBuilder;
public function requestChanges(string $comment = null): ReviewBuilder;
public function comment(string $body): PullRequestActions;
// Labels
public function addLabel(string $label): PullRequestActions;
public function addLabels(array $labels): PullRequestActions;
public function removeLabel(string $label): PullRequestActions;
public function setLabels(array $labels): PullRequestActions;
// Reviewers
public function requestReview(string $username): PullRequestActions;
public function requestReviews(array $usernames): PullRequestActions;
public function requestTeamReview(string $teamSlug): PullRequestActions;
// Assignees
public function assign(string $username): PullRequestActions;
public function unassign(string $username): PullRequestActions;
// Merge operations
public function merge(string $strategy = 'merge', ?string $message = null): PullRequestActions;
public function squashMerge(?string $message = null): PullRequestActions;
public function rebaseMerge(): PullRequestActions;
// Queries
public function reviews(): ReviewQuery;
public function checks(): CheckRunQuery;
public function files(): FileQuery;
public function commits(): CommitQuery;
// Helpers
public function checksPass(): bool;
public function checksFail(): bool;
public function isApproved(): bool;
public function hasChangesRequested(): bool;
public function approvalCount(): int;
Related Data Objects
// User DTO
final readonly class User
{
public function __construct(
public int $id,
public string $login,
public string $type, // 'User' | 'Bot'
public string $avatarUrl,
public string $htmlUrl,
) {}
}
// Label DTO
final readonly class Label
{
public function __construct(
public int $id,
public string $name,
public string $color,
public string $description,
) {}
}
Technical Implementation
Pattern: DTO returns action builders
// The DTO is readonly and immutable
// Actions return builder instances that execute operations
final readonly class PullRequest
{
// ... properties
public function approve(string $comment = null): ReviewBuilder
{
return (new ReviewBuilder($this->github, $this->fullName, $this->number))
->approve($comment);
}
public function merge(string $strategy = 'merge', ?string $message = null): PullRequestActions
{
return (new PullRequestActions($this->github, $this->fullName, $this->number))
->merge($strategy, $message);
}
}
Usage Examples
$pr = PullRequests::find('owner/repo', 123);
// Access properties
echo $pr->title;
echo $pr->state;
echo $pr->author->login;
// Chain actions
$pr->approve('LGTM!')
->merge(strategy: 'squash');
// Query related data
$reviews = $pr->reviews()->get();
$checks = $pr->checks()->whereFailed()->get();
// Helpers
if ($pr->checksPass() && $pr->isApproved()) {
$pr->merge();
}
Testing Requirements
it('creates PR from array')
->expect(fn() => PullRequest::fromArray($apiResponse))
->toBeInstanceOf(PullRequest::class);
it('has readonly properties')
->expect(fn() => $pr->title = 'new')
->toThrow(Error::class);
it('returns action builders')
->expect(fn() => $pr->approve())
->toBeInstanceOf(ReviewBuilder::class);
it('chains multiple actions')
->expect(fn() =>
$pr->addLabel('ready')
->requestReview('senior-dev')
->comment('Ready for review')
)->toBeInstanceOf(PullRequestActions::class);
Dependencies
- conduit-ui/connector
- Carbon for date handling
- Laravel Collections
References
Labels
- enhancement
- core
- data-objects
Summary
Create strongly-typed
PullRequestdata transfer object with chainable action methods for individual PR operations.Acceptance Criteria
DTO Properties
Chainable Action Methods
The DTO should return action builder instances for chainable operations:
Related Data Objects
Technical Implementation
Pattern: DTO returns action builders
Usage Examples
Testing Requirements
Dependencies
References
Labels