-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
273 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<?php | ||
|
||
namespace App\Command; | ||
|
||
use App\Api\Issue\IssueApi; | ||
use App\Entity\Task; | ||
use App\Service\RepositoryProvider; | ||
use App\Service\TaskScheduler; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
/** | ||
* Close issues not been updated in a long while. | ||
* | ||
* @author Tobias Nyholm <[email protected]> | ||
*/ | ||
class CloseStaleIssuesCommand extends Command | ||
{ | ||
protected static $defaultName = 'app:issue:close-stale'; | ||
private $repositoryProvider; | ||
private $issueApi; | ||
private $scheduler; | ||
|
||
public function __construct(RepositoryProvider $repositoryProvider, IssueApi $issueApi, TaskScheduler $scheduler) | ||
{ | ||
parent::__construct(); | ||
$this->repositoryProvider = $repositoryProvider; | ||
$this->issueApi = $issueApi; | ||
$this->scheduler = $scheduler; | ||
} | ||
|
||
protected function configure() | ||
{ | ||
$this->addArgument('repository', InputArgument::REQUIRED, 'The full name to the repository, eg symfony/symfony-docs'); | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output) | ||
{ | ||
/** @var string $repositoryName */ | ||
$repositoryName = $input->getArgument('repository'); | ||
$repository = $this->repositoryProvider->getRepository($repositoryName); | ||
if (null === $repository) { | ||
$output->writeln('Repository not configured'); | ||
|
||
return 1; | ||
} | ||
|
||
$notUpdatedAfter = new \DateTimeImmutable('-12months'); | ||
$issues = $this->issueApi->findStaleIssues($repository, $notUpdatedAfter); | ||
|
||
foreach ($issues as $issue) { | ||
$this->issueApi->commentOnIssue($repository, $issue['number'], <<<TXT | ||
Hey, | ||
Is this issue still relevant? It has not been any activity in a while. I will close this if nobody makes a comment soon. | ||
Cheers! | ||
Carsonbot | ||
TXT | ||
); | ||
|
||
// add a scheduled task to process this issue again after 2 weeks | ||
$this->scheduler->runLater($repository, $issue['number'], Task::ACTION_CLOSE_STALE, new \DateTimeImmutable('+2weeks')); | ||
} | ||
|
||
return 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Service\TaskHandler; | ||
|
||
use App\Api\Issue\IssueApi; | ||
use App\Api\Label\LabelApi; | ||
use App\Entity\Task; | ||
use App\Service\RepositoryProvider; | ||
|
||
/** | ||
* @author Tobias Nyholm <[email protected]> | ||
*/ | ||
class CloseStaleIssuesHandler implements TaskHandlerInterface | ||
{ | ||
private $issueApi; | ||
private $repositoryProvider; | ||
private $labelApi; | ||
|
||
public function __construct(LabelApi $labelApi, IssueApi $issueApi, RepositoryProvider $repositoryProvider) | ||
{ | ||
$this->issueApi = $issueApi; | ||
$this->repositoryProvider = $repositoryProvider; | ||
$this->labelApi = $labelApi; | ||
} | ||
|
||
/** | ||
* Close the issue if the last comment was made by the bot and if "Keep open" label does not exist. | ||
*/ | ||
public function handle(Task $task): void | ||
{ | ||
if (null === $repository = $this->repositoryProvider->getRepository($task->getRepositoryFullName())) { | ||
return; | ||
} | ||
$labels = $this->labelApi->getIssueLabels($task->getNumber(), $repository); | ||
if (in_array('Keep open', $labels)) { | ||
return; | ||
} | ||
|
||
if ($this->issueApi->lastCommentWasMadeByBot($repository, $task->getNumber())) { | ||
$this->issueApi->close($repository, $task->getNumber()); | ||
} | ||
} | ||
|
||
public function supports(Task $task): bool | ||
{ | ||
return Task::ACTION_CLOSE_STALE === $task->getAction(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Service; | ||
|
||
use App\Entity\Task; | ||
use App\Model\Repository; | ||
use App\Repository\TaskRepository; | ||
|
||
/** | ||
* Schedule a job to run later. | ||
* | ||
* @author Tobias Nyholm <[email protected]> | ||
*/ | ||
class TaskScheduler | ||
{ | ||
private $taskRepo; | ||
|
||
public function __construct(TaskRepository $taskRepo) | ||
{ | ||
$this->taskRepo = $taskRepo; | ||
} | ||
|
||
public function runLater(Repository $repository, $number, int $action, \DateTimeImmutable $checkAt) | ||
{ | ||
$task = new Task($repository->getFullName(), $number, $action, $checkAt); | ||
$this->taskRepo->persist($task); | ||
$this->taskRepo->flush(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Service\TaskHandler; | ||
|
||
use App\Api\Issue\NullIssueApi; | ||
use App\Api\Label\NullLabelApi; | ||
use App\Entity\Task; | ||
use App\Service\RepositoryProvider; | ||
use App\Service\TaskHandler\CloseStaleIssuesHandler; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class CloseStaleIssuesHandlerTest extends TestCase | ||
{ | ||
public function testHandleKeepOpen() | ||
{ | ||
$labelApi = $this->getMockBuilder(NullLabelApi::class) | ||
->disableOriginalConstructor() | ||
->setMethods(['getIssueLabels', 'lastCommentWasMadeByBot']) | ||
->getMock(); | ||
$labelApi->expects($this->any())->method('getIssueLabels')->willReturn(['Bug', 'Keep open']); | ||
|
||
$issueApi = $this->getMockBuilder(NullIssueApi::class) | ||
->disableOriginalConstructor() | ||
->setMethods(['close', 'lastCommentWasMadeByBot']) | ||
->getMock(); | ||
$issueApi->expects($this->any())->method('lastCommentWasMadeByBot')->willReturn(true); | ||
$issueApi->expects($this->never())->method('close'); | ||
|
||
$repoProvider = new RepositoryProvider(['carsonbot-playground/symfony' => []]); | ||
|
||
$handler = new CloseStaleIssuesHandler($labelApi, $issueApi, $repoProvider); | ||
$handler->handle(new Task('carsonbot-playground/symfony', 4711, Task::ACTION_CLOSE_STALE, new \DateTimeImmutable())); | ||
} | ||
|
||
public function testHandleComments() | ||
{ | ||
$labelApi = $this->getMockBuilder(NullLabelApi::class) | ||
->disableOriginalConstructor() | ||
->setMethods(['getIssueLabels', 'lastCommentWasMadeByBot']) | ||
->getMock(); | ||
$labelApi->expects($this->any())->method('getIssueLabels')->willReturn(['Bug']); | ||
|
||
$issueApi = $this->getMockBuilder(NullIssueApi::class) | ||
->disableOriginalConstructor() | ||
->setMethods(['close', 'lastCommentWasMadeByBot']) | ||
->getMock(); | ||
$issueApi->expects($this->any())->method('lastCommentWasMadeByBot')->willReturn(false); | ||
$issueApi->expects($this->never())->method('close'); | ||
|
||
$repoProvider = new RepositoryProvider(['carsonbot-playground/symfony' => []]); | ||
|
||
$handler = new CloseStaleIssuesHandler($labelApi, $issueApi, $repoProvider); | ||
$handler->handle(new Task('carsonbot-playground/symfony', 4711, Task::ACTION_CLOSE_STALE, new \DateTimeImmutable())); | ||
} | ||
|
||
public function testHandleStale() | ||
{ | ||
$labelApi = $this->getMockBuilder(NullLabelApi::class) | ||
->disableOriginalConstructor() | ||
->setMethods(['getIssueLabels']) | ||
->getMock(); | ||
$labelApi->expects($this->any())->method('getIssueLabels')->willReturn(['Bug']); | ||
|
||
$issueApi = $this->getMockBuilder(NullIssueApi::class) | ||
->disableOriginalConstructor() | ||
->setMethods(['close', 'lastCommentWasMadeByBot']) | ||
->getMock(); | ||
$issueApi->expects($this->any())->method('lastCommentWasMadeByBot')->willReturn(true); | ||
$issueApi->expects($this->once())->method('close'); | ||
|
||
$repoProvider = new RepositoryProvider(['carsonbot-playground/symfony' => []]); | ||
|
||
$handler = new CloseStaleIssuesHandler($labelApi, $issueApi, $repoProvider); | ||
$handler->handle(new Task('carsonbot-playground/symfony', 4711, Task::ACTION_CLOSE_STALE, new \DateTimeImmutable())); | ||
} | ||
} |