-
Notifications
You must be signed in to change notification settings - Fork 31
Added support for GitHub reviews #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
f5edb09
632ccc1
0f4b4fe
44e7757
61c8b6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
namespace AppBundle\Subscriber; | ||
|
||
use AppBundle\Issues\Status; | ||
use AppBundle\Issues\StatusApi; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
|
||
abstract class AbstractStatusChangeSubscriber implements EventSubscriberInterface | ||
{ | ||
protected static $triggerWordToStatus = [ | ||
'needs review' => Status::NEEDS_REVIEW, | ||
'needs work' => Status::NEEDS_WORK, | ||
'works for me' => Status::WORKS_FOR_ME, | ||
'reviewed' => Status::REVIEWED, | ||
]; | ||
|
||
protected $statusApi; | ||
|
||
public function __construct(StatusApi $statusApi) | ||
{ | ||
$this->statusApi = $statusApi; | ||
} | ||
|
||
/** | ||
* Parses the text and looks for keywords to see if this should cause any | ||
* status change. | ||
* | ||
* @param string $body | ||
* | ||
* @return null|string | ||
*/ | ||
protected function parseStatusFromText($body) | ||
{ | ||
$triggerWord = implode('|', array_keys(static::$triggerWordToStatus)); | ||
$formatting = '[\\s\\*]*'; | ||
// Match first character after "status:" | ||
// Case insensitive ("i"), ignores formatting with "*" before or after the ":" | ||
$pattern = "~(?=\n|^)${formatting}status${formatting}:${formatting}[\"']?($triggerWord)[\"']?${formatting}[.!]?${formatting}(?<=\r\n|\n|$)~i"; | ||
|
||
if (preg_match_all($pattern, $body, $matches)) { | ||
// Second subpattern = first status character | ||
return static::$triggerWordToStatus[strtolower(end($matches[1]))]; | ||
} | ||
} | ||
|
||
private function checkUserIsAllowedToReview(array $data) | ||
{ | ||
return $data['issue']['user']['login'] !== $data['comment']['user']['login']; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
<?php | ||
|
||
namespace AppBundle\Subscriber; | ||
|
||
use AppBundle\Event\GitHubEvent; | ||
use AppBundle\GitHubEvents; | ||
use AppBundle\Issues\Status; | ||
use AppBundle\Issues\StatusApi; | ||
use Psr\Log\LoggerInterface; | ||
|
||
/** | ||
* Changes the status when a new review is submitted. | ||
* | ||
* @author Wouter de Jong <wouter@wouterj.nl> | ||
*/ | ||
class StatusChangeByReviewSubscriber extends AbstractStatusChangeSubscriber | ||
{ | ||
private $logger; | ||
|
||
public function __construct(StatusApi $statusApi, LoggerInterface $logger) | ||
{ | ||
parent::__construct($statusApi); | ||
$this->logger = $logger; | ||
} | ||
|
||
/** | ||
* Sets the status based on the review state (approved/changes requested) | ||
* or the review body (using the Status: keyword). | ||
* | ||
* @param GithubEvent $event | ||
*/ | ||
public function onReview(GitHubEvent $event) | ||
{ | ||
$data = $event->getData(); | ||
if ('submitted' !== $data['action']) { | ||
$event->setResponseData(array('unsupported_action' => $data['action'])); | ||
|
||
return; | ||
} | ||
|
||
$repository = $event->getRepository(); | ||
$pullRequestNumber = $data['pull_request']['number']; | ||
$newStatus = null; | ||
|
||
// Set status based on review state | ||
switch (strtolower($data['review']['state'])) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GitHub docs are a bit vague on this: REST api docs shows state in all uppercase, while the webhooks docs show it in lowercase. Just to be sure, I've added this |
||
case 'approved': | ||
$newStatus = Status::REVIEWED; | ||
|
||
break; | ||
case 'changes_requested': | ||
$newStatus = Status::NEEDS_WORK; | ||
|
||
break; | ||
default: | ||
$newStatus = $this->parseStatusFromText($data['review']['body']); | ||
|
||
if (Status::REVIEWED === $newStatus && false === $this->checkUserIsAllowedToReview($data)) { | ||
$newStatus = null; | ||
} | ||
} | ||
|
||
$event->setResponseData(array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about moving the beginning of the method and those line in the abstract listener too, it could call an abstract method to extract the status from the payload, wdyt? |
||
'pull_request' => $pullRequestNumber, | ||
'status_change' => $newStatus, | ||
)); | ||
|
||
if (null === $newStatus) { | ||
return; | ||
} | ||
|
||
$this->logger->debug(sprintf('Setting issue number %s to status %s', $pullRequestNumber, $newStatus)); | ||
$this->statusApi->setIssueStatus($pullRequestNumber, $newStatus, $repository); | ||
} | ||
|
||
/** | ||
* Sets the status to needs review when a review is requested. | ||
* | ||
* @param GithubEvent $event | ||
*/ | ||
public function onReviewRequested(GithubEvent $event) | ||
{ | ||
$data = $event->getData(); | ||
if ('review_requested' !== $data['action']) { | ||
$event->setResponseData(array('unsupported_action' => $data['action'])); | ||
|
||
return; | ||
} | ||
|
||
$repository = $event->getRepository(); | ||
$pullRequestNumber = $data['pull_request']['number']; | ||
$newStatus = Status::NEEDS_REVIEW; | ||
|
||
$this->logger->debug(sprintf('Setting issue number %s to status %s', $pullRequestNumber, $newStatus)); | ||
$this->statusApi->setIssueStatus($pullRequestNumber, $newStatus, $repository); | ||
|
||
$event->setResponseData(array( | ||
'pull_request' => $pullRequestNumber, | ||
'status_change' => $newStatus, | ||
)); | ||
} | ||
|
||
public static function getSubscribedEvents() | ||
{ | ||
return array( | ||
GitHubEvents::PULL_REQUEST_REVIEW => 'onReview', | ||
GitHubEvents::PULL_REQUEST => 'onReviewRequested', | ||
); | ||
} | ||
|
||
private function checkUserIsAllowedToReview(array $data) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is also defined in AbstractStatusChangeSubscriber, and check implies it will throw exception or something. By Symfony conventions it should be |
||
{ | ||
return $data['pull_request']['user']['login'] !== $data['review']['user']['login']; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI you don't have to escape special characters in a character class, except
-
if it's not at the end and to be used as literal.