Skip to content

Commit e58dc43

Browse files
committed
feature #110 Suggest reviewers action (Nyholm)
This PR was merged into the master branch. Discussion ---------- Suggest reviewers action This PR brings two features: 1. Someone can make a comment in a PR to say `@carsonbot find me a reviewer please` and a minute later it will reply with a ping to someone. 2. If a PR is opened an had no comments/reviews in 24(?) hours, then we write a comment with a ping to someone. This PR works with a Github action that clones the [git-reviewer script](https://github.com/Nyholm/git-reviewer), checks out the source (symfony/symfony or symfony/symfony-docs) and then runs the `git-reviewer`. That will produce a group of users that also have modified the changed files in the PR. I ignore FrameworkBundle, all tests and the Changelogs because they are always changed. I also only looking for contributors for the past 2 years. When the `git-reviewer` is done, we run the `bin/console app:review:suggest` command. It looks at the contributor lists, removes people (core team) from a block list and removes all people that already have interacted with the PR. Then it just writes a comment. Commits ------- 4ba7344 Suggest reviewers action
2 parents 6b46248 + 4ba7344 commit e58dc43

19 files changed

+922
-5
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ APP_SECRET=5dd8ffca252d95e8b4fb5b2d15310e92
2121

2222
SYMFONY_DOCS_SECRET=''
2323
SYMFONY_SECRET=''
24-
BOT_USERNAME='carsonbot-test'
24+
BOT_USERNAME='carsonbot'
2525
###> knplabs/github-api ###
2626
#GITHUB_TOKEN=XXX
2727
###< knplabs/github-api ###

.github/workflows/find-reviewer.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Find Reviewer
2+
3+
on:
4+
repository_dispatch:
5+
types: [find-reviewer]
6+
7+
jobs:
8+
find:
9+
name: Search
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v2
15+
16+
- name: Create path
17+
run: mkdir -p build/reviewer
18+
19+
- name: Get composer cache directory
20+
id: composer-cache
21+
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
22+
23+
- name: Cache dependencies
24+
uses: actions/cache@v2
25+
with:
26+
path: ${{ steps.composer-cache.outputs.dir }}
27+
key: composer-${{ runner.os }}-7.4-${{ hashFiles('composer.*') }}
28+
restore-keys: |
29+
composer-${{ runner.os }}-7.4-
30+
composer-${{ runner.os }}-
31+
composer-
32+
33+
- name: Download dependencies
34+
run: composer install --no-interaction --optimize-autoloader
35+
36+
- name: Cache dependencies
37+
uses: actions/cache@v2
38+
with:
39+
path: build/reviewer/var
40+
key: nyholm-git-reviewer
41+
42+
- name: Checkout GitReviewer repo
43+
run: |
44+
mkdir -p build/reviewer/var
45+
mv build/reviewer/var build/reviewer_tmp
46+
composer create-project nyholm/git-reviewer build/reviewer
47+
mv build/reviewer_tmp build/reviewer/var
48+
49+
- name: Download dependencies
50+
run: |
51+
cd build/reviewer
52+
composer update --no-interaction --prefer-dist --optimize-autoloader --prefer-stable
53+
54+
- name: Checkout target repo
55+
id: target-repo
56+
run: |
57+
git clone https://github.com/${{ github.event.client_payload.repository }} build/target/${{ github.event.client_payload.repository }}
58+
echo "::set-output name=dir::$(pwd)/build/target/${{ github.event.client_payload.repository }}"
59+
60+
- name: Find branch base
61+
id: target-base
62+
run: |
63+
cd build/reviewer
64+
BASE=$(./git-reviewer.php pull-request:base ${{ github.event.client_payload.pull_request_number }} ${{ steps.target-repo.outputs.dir }})
65+
echo "::set-output name=branch::$BASE"
66+
67+
- name: Checkout branch base
68+
run: |
69+
cd ${{ steps.target-repo.outputs.dir }}
70+
echo ${{ steps.target-base.outputs.branch }}
71+
git pull
72+
git checkout ${{ steps.target-base.outputs.branch }}
73+
74+
- name: Find reviwers
75+
env:
76+
GITHUB_TOKEN: ${{ secrets.CARSONPROD_GITHUB_TOKEN }}
77+
run: |
78+
cd build/reviewer
79+
./git-reviewer.php find ${{ github.event.client_payload.pull_request_number }} ${{ steps.target-repo.outputs.dir }} \
80+
--after `date +%Y-%m-%d --date="2 year ago"` \
81+
--ignore-path "src/Symfony/FrameworkBundle/*" \
82+
--ignore-path "src/Symfony/Bundle/FrameworkBundle/*" \
83+
--ignore-path "src/**/Tests/*" \
84+
--ignore-path CHANGELOG*.md \
85+
--ignore-path UPGRADE*.md \
86+
--pretty-print > output.json
87+
88+
cat output.json
89+
90+
- name: Write comment
91+
env:
92+
GITHUB_TOKEN: ${{ secrets.CARSONPROD_GITHUB_TOKEN }}
93+
run: bin/console app:review:suggest ${{ github.event.client_payload.repository }} ${{ github.event.client_payload.pull_request_number }} ${{ github.event.client_payload.type }} `pwd`/build/reviewer/output.json

config/services.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ parameters:
77
- 'App\Subscriber\StatusChangeByReviewSubscriber'
88
- 'App\Subscriber\NeedsReviewNewPRSubscriber'
99
- 'App\Subscriber\BugLabelNewIssueSubscriber'
10+
- 'App\Subscriber\FindReviewerSubscriber'
1011
- 'App\Subscriber\AutoLabelFromContentSubscriber'
1112
- 'App\Subscriber\MilestoneNewPRSubscriber'
1213
- 'App\Subscriber\WelcomeFirstTimeContributorSubscriber'
@@ -23,6 +24,7 @@ parameters:
2324
- 'App\Subscriber\StatusChangeByReviewSubscriber'
2425
- 'App\Subscriber\NeedsReviewNewPRSubscriber'
2526
- 'App\Subscriber\BugLabelNewIssueSubscriber'
27+
- 'App\Subscriber\FindReviewerSubscriber'
2628
- 'App\Subscriber\AutoLabelFromContentSubscriber'
2729
- 'App\Subscriber\UnsupportedBranchSubscriber'
2830
- 'subscriber.symfony_docs.milestone'
@@ -39,6 +41,7 @@ parameters:
3941
- 'App\Subscriber\StatusChangeByReviewSubscriber'
4042
- 'App\Subscriber\NeedsReviewNewPRSubscriber'
4143
- 'App\Subscriber\BugLabelNewIssueSubscriber'
44+
- 'App\Subscriber\FindReviewerSubscriber'
4245
- 'App\Subscriber\AutoLabelFromContentSubscriber'
4346
- 'App\Subscriber\MilestoneNewPRSubscriber'
4447
- 'App\Subscriber\WelcomeFirstTimeContributorSubscriber'
@@ -51,7 +54,6 @@ services:
5154
_defaults:
5255
autowire: true
5356
autoconfigure: true
54-
5557
bind:
5658
string $botUsername: '%env(BOT_USERNAME)%'
5759

src/Api/Issue/GithubIssueApi.php

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@
55
use App\Model\Repository;
66
use Github\Api\Issue;
77
use Github\Api\Issue\Comments;
8+
use Github\Api\Issue\Timeline;
9+
use Github\Api\PullRequest\Review;
810
use Github\Api\Search;
11+
use Github\Exception\RuntimeException;
912

1013
class GithubIssueApi implements IssueApi
1114
{
1215
private $issueCommentApi;
13-
private $botUsername;
16+
private $reviewApi;
1417
private $issueApi;
1518
private $searchApi;
19+
private $timelineApi;
20+
private $botUsername;
1621

17-
public function __construct(Comments $issueCommentApi, Issue $issueApi, Search $searchApi, string $botUsername)
22+
public function __construct(Comments $issueCommentApi, Review $reviewApi, Issue $issueApi, Search $searchApi, Timeline $timelineApi, string $botUsername)
1823
{
1924
$this->issueCommentApi = $issueCommentApi;
25+
$this->reviewApi = $reviewApi;
2026
$this->issueApi = $issueApi;
2127
$this->searchApi = $searchApi;
28+
$this->timelineApi = $timelineApi;
2229
$this->botUsername = $botUsername;
2330
}
2431

@@ -52,6 +59,31 @@ public function lastCommentWasMadeByBot(Repository $repository, $number): bool
5259
return $this->botUsername === ($lastComment['user']['login'] ?? null);
5360
}
5461

62+
/**
63+
* Has this PR or issue comments/reviews from others than the author?
64+
*/
65+
public function hasActivity(Repository $repository, $number): bool
66+
{
67+
$issue = $this->issueApi->show($repository->getVendor(), $repository->getName(), $number);
68+
$author = $issue['user']['login'] ?? null;
69+
70+
try {
71+
$reviewComments = $this->reviewApi->all($repository->getVendor(), $repository->getName(), $number);
72+
} catch (RuntimeException $e) {
73+
// This was not a PR =)
74+
$reviewComments = [];
75+
}
76+
77+
$all = array_merge($reviewComments, $this->issueCommentApi->all($repository->getVendor(), $repository->getName(), $number));
78+
foreach ($all as $comment) {
79+
if (!in_array($comment['user']['login'], [$author, $this->botUsername])) {
80+
return true;
81+
}
82+
}
83+
84+
return false;
85+
}
86+
5587
public function show(Repository $repository, $issueNumber): array
5688
{
5789
return $this->issueApi->show($repository->getVendor(), $repository->getName(), $issueNumber);
@@ -81,4 +113,23 @@ public function findStaleIssues(Repository $repository, \DateTimeImmutable $noUp
81113

82114
return $issues['items'] ?? [];
83115
}
116+
117+
public function getUsers(Repository $repository, $issueNumber): array
118+
{
119+
$timeline = $this->timelineApi->all($repository->getVendor(), $repository->getName(), $issueNumber);
120+
$users = [];
121+
foreach ($timeline as $event) {
122+
$users[] = $event['actor']['login'] ?? $event['user']['login'] ?? $event['author']['email'] ?? '';
123+
if (isset($event['body'])) {
124+
// Parse body for user reference
125+
if (preg_match_all('|@([a-zA-z_\-0-9]+)|', $event['body'], $matches)) {
126+
foreach ($matches[1] as $match) {
127+
$users[] = $match;
128+
}
129+
}
130+
}
131+
}
132+
133+
return array_map(function ($a) { return strtolower($a); }, array_unique($users));
134+
}
84135
}

src/Api/Issue/IssueApi.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public function show(Repository $repository, $issueNumber): array;
2121

2222
public function commentOnIssue(Repository $repository, $issueNumber, string $commentBody);
2323

24+
public function hasActivity(Repository $repository, $number): bool;
25+
2426
public function lastCommentWasMadeByBot(Repository $repository, $number): bool;
2527

2628
public function findStaleIssues(Repository $repository, \DateTimeImmutable $noUpdateAfter): array;
@@ -29,4 +31,9 @@ public function findStaleIssues(Repository $repository, \DateTimeImmutable $noUp
2931
* Close an issue or a pull request.
3032
*/
3133
public function close(Repository $repository, $issueNumber);
34+
35+
/**
36+
* Get users active or mentioned in this issue/pull request.
37+
*/
38+
public function getUsers(Repository $repository, $issueNumber): array;
3239
}

src/Api/Issue/NullIssueApi.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public function commentOnIssue(Repository $repository, $issueNumber, string $com
1919
{
2020
}
2121

22+
public function hasActivity(Repository $repository, $number): bool
23+
{
24+
return false;
25+
}
26+
2227
public function lastCommentWasMadeByBot(Repository $repository, $number): bool
2328
{
2429
return false;
@@ -32,4 +37,9 @@ public function findStaleIssues(Repository $repository, \DateTimeImmutable $noUp
3237
public function close(Repository $repository, $issueNumber)
3338
{
3439
}
40+
41+
public function getUsers(Repository $repository, $issueNumber): array
42+
{
43+
return [];
44+
}
3545
}

src/Api/Issue/StdErrIssueApi.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace App\Api\Issue;
4+
5+
use App\Model\Repository;
6+
7+
/**
8+
* This will not write to Github, only output to STDERR. But it will try to read from Github.
9+
*
10+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
11+
*/
12+
class StdErrIssueApi extends GithubIssueApi
13+
{
14+
public function open(Repository $repository, string $title, string $body, array $labels)
15+
{
16+
error_log(sprintf('Open new issue on %s', $repository->getFullName()));
17+
error_log('Title: '.$title);
18+
error_log('Labels: '.json_encode($labels));
19+
error_log($body);
20+
}
21+
22+
public function close(Repository $repository, $issueNumber)
23+
{
24+
error_log(sprintf('Closing %s#%d', $repository->getFullName(), $issueNumber));
25+
}
26+
27+
public function commentOnIssue(Repository $repository, $issueNumber, string $commentBody)
28+
{
29+
error_log(sprintf('Commenting on %s#%d', $repository->getFullName(), $issueNumber));
30+
error_log($commentBody);
31+
}
32+
}

0 commit comments

Comments
 (0)