Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/Contracts/AssigneeManagerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\Contracts;

use Illuminate\Support\Collection;

interface AssigneeManagerInterface
{
/**
* Get all assignees for the pull request.
*
* @return Collection<int, \ConduitUI\Pr\DataTransferObjects\User>
*/
public function get(): Collection;

/**
* Add a single assignee.
*/
public function add(string $username): self;

/**
* Add multiple assignees.
*
* @param array<int, string> $usernames
*/
public function addMany(array $usernames): self;

/**
* Remove a single assignee.
*/
public function remove(string $username): self;

/**
* Remove multiple assignees.
*
* @param array<int, string> $usernames
*/
public function removeMany(array $usernames): self;

/**
* Replace all assignees with new ones.
*
* @param array<int, string> $usernames
*/
public function replace(array $usernames): self;

/**
* Clear all assignees.
*/
public function clear(): self;

/**
* Check if a user is assigned.
*/
public function has(string $username): bool;
}
92 changes: 92 additions & 0 deletions src/DataTransferObjects/Milestone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\DataTransferObjects;

use Carbon\Carbon;

class Milestone
{
public function __construct(
public readonly int $number,
public readonly string $title,
public readonly ?string $description,
public readonly string $state,
public readonly int $openIssues,
public readonly int $closedIssues,
public readonly ?Carbon $dueOn,
public readonly Carbon $createdAt,
public readonly Carbon $updatedAt,
public readonly ?Carbon $closedAt,
public readonly string $htmlUrl,
) {}

/**
* @param array<string, mixed> $data
*/
public static function fromArray(array $data): self
{
return new self(
number: $data['number'],
title: $data['title'],
description: $data['description'] ?? null,
state: $data['state'],
openIssues: $data['open_issues'],
closedIssues: $data['closed_issues'],
dueOn: isset($data['due_on']) ? Carbon::parse($data['due_on']) : null,
createdAt: Carbon::parse($data['created_at']),
updatedAt: Carbon::parse($data['updated_at']),
closedAt: isset($data['closed_at']) ? Carbon::parse($data['closed_at']) : null,
htmlUrl: $data['html_url'],
);
}

/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'number' => $this->number,
'title' => $this->title,
'description' => $this->description,
'state' => $this->state,
'open_issues' => $this->openIssues,
'closed_issues' => $this->closedIssues,
'due_on' => $this->dueOn?->toIso8601String(),
'created_at' => $this->createdAt->toIso8601String(),
'updated_at' => $this->updatedAt->toIso8601String(),
'closed_at' => $this->closedAt?->toIso8601String(),
'html_url' => $this->htmlUrl,
];
}

public function isOpen(): bool
{
return $this->state === 'open';
}

public function isClosed(): bool
{
return $this->state === 'closed';
}

public function isOverdue(): bool
{
return $this->dueOn !== null
&& $this->dueOn->isPast()
&& $this->isOpen();
}

public function progress(): float
{
$total = $this->openIssues + $this->closedIssues;

if ($total === 0) {
return 0.0;
}

return round(($this->closedIssues / $total) * 100, 2);
}
}
28 changes: 28 additions & 0 deletions src/PullRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
use ConduitUI\Pr\Requests\RemoveReviewers;
use ConduitUI\Pr\Requests\RequestReviewers;
use ConduitUI\Pr\Requests\UpdatePullRequest;
use ConduitUI\Pr\Services\AssigneeManager;
use ConduitUI\Pr\Services\MilestoneManager;
use ConduitUI\Pr\Services\ReviewBuilder;
use ConduitUI\Pr\Services\ReviewQuery;

Expand Down Expand Up @@ -471,6 +473,32 @@ public function timeline(): array
return $allEvents;
}

/**
* Get an assignee manager for this pull request.
*/
public function assignees(): AssigneeManager
{
return new AssigneeManager($this->connector, "{$this->owner}/{$this->repo}", $this->data->number);
}

/**
* Get a milestone manager for this pull request.
*/
public function milestone(): MilestoneManager
{
return new MilestoneManager($this->connector, "{$this->owner}/{$this->repo}", $this->data->number);
}

/**
* Set the milestone for this pull request.
*/
public function setMilestone(int $milestoneNumber): static
{
$this->milestone()->set($milestoneNumber);

return $this;
}

public function __get(string $name): mixed
{
return $this->data->{$name}; // @phpstan-ignore-line Variable property access is intentional for magic getter
Expand Down
39 changes: 39 additions & 0 deletions src/Requests/CreateMilestone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\Requests;

use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;

class CreateMilestone extends Request implements HasBody
{
use HasJsonBody;

protected Method $method = Method::POST;

/**
* @param array<string, mixed> $data
*/
public function __construct(
protected string $owner,
protected string $repo,
protected array $data,
) {}

public function resolveEndpoint(): string
{
return "/repos/{$this->owner}/{$this->repo}/milestones";
}

/**
* @return array<string, mixed>
*/
protected function defaultBody(): array
{
return $this->data;
}
}
24 changes: 24 additions & 0 deletions src/Requests/DeleteMilestone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;

class DeleteMilestone extends Request
{
protected Method $method = Method::DELETE;

public function __construct(
protected string $owner,
protected string $repo,
protected int $number,
) {}

public function resolveEndpoint(): string
{
return "/repos/{$this->owner}/{$this->repo}/milestones/{$this->number}";
}
}
24 changes: 24 additions & 0 deletions src/Requests/GetMilestone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;

class GetMilestone extends Request
{
protected Method $method = Method::GET;

public function __construct(
protected string $owner,
protected string $repo,
protected int $number,
) {}

public function resolveEndpoint(): string
{
return "/repos/{$this->owner}/{$this->repo}/milestones/{$this->number}";
}
}
32 changes: 32 additions & 0 deletions src/Requests/ListMilestones.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;

class ListMilestones extends Request
{
protected Method $method = Method::GET;

public function __construct(
protected string $owner,
protected string $repo,
protected string $state = 'all',
) {}

public function resolveEndpoint(): string
{
return "/repos/{$this->owner}/{$this->repo}/milestones";
}

/**
* @return array<string, mixed>
*/
protected function defaultQuery(): array
{
return ['state' => $this->state];
}
}
40 changes: 40 additions & 0 deletions src/Requests/UpdateMilestone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace ConduitUI\Pr\Requests;

use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;

class UpdateMilestone extends Request implements HasBody
{
use HasJsonBody;

protected Method $method = Method::PATCH;

/**
* @param array<string, mixed> $data
*/
public function __construct(
protected string $owner,
protected string $repo,
protected int $number,
protected array $data,
) {}

public function resolveEndpoint(): string
{
return "/repos/{$this->owner}/{$this->repo}/milestones/{$this->number}";
}

/**
* @return array<string, mixed>
*/
protected function defaultBody(): array
{
return $this->data;
}
}
Loading
Loading