Skip to content

Commit f5edb09

Browse files
committed
Added support for GitHub reviews
1 parent d86ec51 commit f5edb09

File tree

6 files changed

+340
-33
lines changed

6 files changed

+340
-33
lines changed

app/config/github.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,19 @@ services:
2626
arguments:
2727
- '@app.github.cached_labels_api'
2828

29+
app.subscriber.status_change_by_review_subscriber:
30+
class: AppBundle\Subscriber\StatusChangeByReviewSubscriber
31+
arguments:
32+
- '@app.status_api'
33+
- '@logger'
34+
2935
parameters:
3036
# point to the main symfony repositories
3137
repositories:
3238
symfony/symfony:
3339
subscribers:
3440
- app.subscriber.status_change_by_comment_subscriber
41+
- app.subscriber.status_change_by_review_subscriber
3542
- app.subscriber.needs_review_new_pr_subscriber
3643
- app.subscriber.bug_label_new_issue_subscriber
3744
- app.subscriber.auto_label_pr_from_content_subscriber
@@ -41,6 +48,7 @@ parameters:
4148
subscribers:
4249
- app.subscriber.status_change_by_comment_subscriber
4350
- app.subscriber.status_change_on_push_subscriber
51+
- app.subscriber.status_change_by_review_subscriber
4452
- app.subscriber.needs_review_new_pr_subscriber
4553
- app.subscriber.bug_label_new_issue_subscriber
4654
- app.subscriber.auto_label_pr_from_content_subscriber
@@ -51,6 +59,7 @@ parameters:
5159
subscribers:
5260
- app.subscriber.status_change_by_comment_subscriber
5361
- app.subscriber.status_change_on_push_subscriber
62+
- app.subscriber.status_change_by_review_subscriber
5463
- app.subscriber.needs_review_new_pr_subscriber
5564
- app.subscriber.bug_label_new_issue_subscriber
5665
- app.subscriber.auto_label_pr_from_content_subscriber

src/AppBundle/GitHubEvents.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ final class GitHubEvents
5353
/** @Event('\AppBundle\Event\GitHubEvent') */
5454
const PR_REVIEW_COMMENT = 'github.pull_request_review_comment';
5555

56+
/** @Event('\AppBundle\Event\GithubEvent') */
57+
const PULL_REQUEST_REVIEW = 'github.pull_request_review';
58+
5659
/** @Event('\AppBundle\Event\GitHubEvent') */
5760
const PULL_REQUEST = 'github.pull_request';
5861

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace AppBundle\Subscriber;
4+
5+
use AppBundle\Issues\Status;
6+
use AppBundle\Issues\StatusApi;
7+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8+
9+
abstract class AbstractStatusChangeSubscriber implements EventSubscriberInterface
10+
{
11+
protected static $triggerWordToStatus = [
12+
'needs review' => Status::NEEDS_REVIEW,
13+
'needs work' => Status::NEEDS_WORK,
14+
'works for me' => Status::WORKS_FOR_ME,
15+
'reviewed' => Status::REVIEWED,
16+
];
17+
18+
protected $statusApi;
19+
20+
public function __construct(StatusApi $statusApi)
21+
{
22+
$this->statusApi = $statusApi;
23+
}
24+
25+
/**
26+
* Parses the text and looks for keywords to see if this should cause any
27+
* status change.
28+
*
29+
* @param string $body
30+
*
31+
* @return null|string
32+
*/
33+
protected function parseStatusFromText($body)
34+
{
35+
$triggerWord = implode('|', array_keys(static::$triggerWordToStatus));
36+
$formatting = '[\\s\\*]*';
37+
// Match first character after "status:"
38+
// Case insensitive ("i"), ignores formatting with "*" before or after the ":"
39+
$pattern = "~(?=\n|^)${formatting}status${formatting}:${formatting}[\"']?($triggerWord)[\"']?${formatting}[.!]?${formatting}(?<=\r\n|\n|$)~i";
40+
41+
if (preg_match_all($pattern, $body, $matches)) {
42+
// Second subpattern = first status character
43+
return static::$triggerWordToStatus[strtolower(end($matches[1]))];
44+
}
45+
}
46+
47+
private function checkUserIsAllowedToReview(array $data)
48+
{
49+
return $data['issue']['user']['login'] !== $data['comment']['user']['login'];
50+
}
51+
}
52+

src/AppBundle/Subscriber/StatusChangeByCommentSubscriber.php

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,14 @@
77
use AppBundle\Issues\Status;
88
use AppBundle\Issues\StatusApi;
99
use Psr\Log\LoggerInterface;
10-
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1110

12-
class StatusChangeByCommentSubscriber implements EventSubscriberInterface
11+
class StatusChangeByCommentSubscriber extends AbstractStatusChangeSubscriber
1312
{
14-
private static $triggerWordToStatus = [
15-
'needs review' => Status::NEEDS_REVIEW,
16-
'needs work' => Status::NEEDS_WORK,
17-
'works for me' => Status::WORKS_FOR_ME,
18-
'reviewed' => Status::REVIEWED,
19-
];
20-
21-
private $statusApi;
2213
private $logger;
2314

2415
public function __construct(StatusApi $statusApi, LoggerInterface $logger)
2516
{
26-
$this->statusApi = $statusApi;
17+
parent::__construct($statusApi);
2718
$this->logger = $logger;
2819
}
2920

@@ -37,36 +28,24 @@ public function onIssueComment(GitHubEvent $event)
3728
{
3829
$data = $event->getData();
3930
$repository = $event->getRepository();
40-
$newStatus = null;
4131
$issueNumber = $data['issue']['number'];
32+
$newStatus = $this->parseStatusFromText($data['comment']['body']);
4233

43-
$triggerWord = implode('|', array_keys(static::$triggerWordToStatus));
44-
$formatting = '[\\s\\*]*';
45-
// Match first character after "status:"
46-
// Case insensitive ("i"), ignores formatting with "*" before or after the ":"
47-
$pattern = "~(?=\n|^)${formatting}status${formatting}:${formatting}[\"']?($triggerWord)[\"']?${formatting}[.!]?${formatting}(?<=\r\n|\n|$)~i";
48-
49-
if (preg_match_all($pattern, $data['comment']['body'], $matches)) {
50-
// Second subpattern = first status character
51-
$newStatus = static::$triggerWordToStatus[strtolower(end($matches[1]))];
52-
53-
if (Status::REVIEWED === $newStatus && false === $this->checkUserIsAllowedToReview($data)) {
54-
$event->setResponseData(array(
55-
'issue' => $issueNumber,
56-
'status_change' => null,
57-
));
58-
59-
return;
60-
}
61-
62-
$this->logger->debug(sprintf('Setting issue number %s to status %s', $issueNumber, $newStatus));
63-
$this->statusApi->setIssueStatus($issueNumber, $newStatus, $repository);
34+
if (Status::REVIEWED === $newStatus && false === $this->checkUserIsAllowedToReview($data)) {
35+
$newStatus = null;
6436
}
6537

6638
$event->setResponseData(array(
6739
'issue' => $issueNumber,
6840
'status_change' => $newStatus,
6941
));
42+
43+
if (null === $newStatus) {
44+
return;
45+
}
46+
47+
$this->logger->debug(sprintf('Setting issue number %s to status %s', $issueNumber, $newStatus));
48+
$this->statusApi->setIssueStatus($issueNumber, $newStatus, $repository);
7049
}
7150

7251
public static function getSubscribedEvents()
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace AppBundle\Subscriber;
4+
5+
use AppBundle\Event\GitHubEvent;
6+
use AppBundle\GitHubEvents;
7+
use AppBundle\Issues\Status;
8+
use AppBundle\Issues\StatusApi;
9+
use Psr\Log\LoggerInterface;
10+
11+
/**
12+
* Changes the status when a new review is submitted.
13+
*
14+
* @author Wouter de Jong <wouter@wouterj.nl>
15+
*/
16+
class StatusChangeByReviewSubscriber extends AbstractStatusChangeSubscriber
17+
{
18+
private $logger;
19+
20+
public function __construct(StatusApi $statusApi, LoggerInterface $logger)
21+
{
22+
parent::__construct($statusApi);
23+
$this->logger = $logger;
24+
}
25+
26+
/**
27+
* Sets the status based on the review state (approved/changes requested)
28+
* or the review body (using the Status: keyword).
29+
*
30+
* @param GithubEvent $event
31+
*/
32+
public function onReview(GitHubEvent $event)
33+
{
34+
$data = $event->getData();
35+
if ('submitted' !== $data['action']) {
36+
$event->setResponseData(array('unsupported_action' => $data['action']));
37+
38+
return;
39+
}
40+
41+
$repository = $event->getRepository();
42+
$pullRequestNumber = $data['pull_request']['number'];
43+
$newStatus = null;
44+
45+
// Set status based on review state
46+
switch (strtolower($data['review']['state'])) {
47+
case 'approved':
48+
$newStatus = Status::REVIEWED;
49+
50+
break;
51+
case 'changes_requested':
52+
$newStatus = Status::NEEDS_WORK;
53+
54+
break;
55+
default:
56+
$newStatus = $this->parseStatusFromText($data['review']['body']);
57+
58+
if (Status::REVIEWED === $newStatus && false === $this->checkUserIsAllowedToReview($data)) {
59+
$newStatus = null;
60+
}
61+
}
62+
63+
$event->setResponseData(array(
64+
'pull_request' => $pullRequestNumber,
65+
'status_change' => $newStatus,
66+
));
67+
68+
if (null === $newStatus) {
69+
return;
70+
}
71+
72+
$this->logger->debug(sprintf('Setting issue number %s to status %s', $pullRequestNumber, $newStatus));
73+
$this->statusApi->setIssueStatus($pullRequestNumber, $newStatus, $repository);
74+
}
75+
76+
/**
77+
* Sets the status to needs review when a review is requested.
78+
*
79+
* @param GithubEvent $event
80+
*/
81+
public function onReviewRequested(GithubEvent $event)
82+
{
83+
$data = $event->getData();
84+
if ('review_requested' !== $data['action']) {
85+
$event->setResponseData(array('unsupported_action' => $data['action']));
86+
87+
return;
88+
}
89+
90+
$repository = $event->getRepository();
91+
$pullRequestNumber = $data['pull_request']['number'];
92+
$newStatus = Status::NEEDS_REVIEW;
93+
94+
$this->logger->debug(sprintf('Setting issue number %s to status %s', $pullRequestNumber, $newStatus));
95+
$this->statusApi->setIssueStatus($pullRequestNumber, $newStatus, $repository);
96+
97+
$event->setResponseData(array(
98+
'pull_request' => $pullRequestNumber,
99+
'status_change' => $newStatus,
100+
));
101+
}
102+
103+
public static function getSubscribedEvents()
104+
{
105+
return array(
106+
GitHubEvents::PULL_REQUEST_REVIEW => 'onReview',
107+
GitHubEvents::PULL_REQUEST => 'onReviewRequested',
108+
);
109+
}
110+
111+
private function checkUserIsAllowedToReview(array $data)
112+
{
113+
return $data['pull_request']['user']['login'] !== $data['review']['user']['login'];
114+
}
115+
}

0 commit comments

Comments
 (0)