Skip to content

Commit 73cb80f

Browse files
committed
feature #89 Set labels on issues (Nyholm)
This PR was squashed before being merged into the master branch. Discussion ---------- Set labels on issues Also make sure to get labels from Github Commits ------- d1de4ee CS fixes 397cb51 bugfix 092d5a1 Fixes a78b7af Get labels from github
2 parents 5c78ccc + d1de4ee commit 73cb80f

File tree

8 files changed

+137
-47
lines changed

8 files changed

+137
-47
lines changed

config/packages/cache.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ framework:
1515
#app: cache.adapter.apcu
1616

1717
# Namespaced pools use the above "app" backend by default
18-
#pools:
18+
pools:
1919
#my.dedicated.cache: null

config/services.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ parameters:
66
- 'App\Subscriber\StatusChangeByReviewSubscriber'
77
- 'App\Subscriber\NeedsReviewNewPRSubscriber'
88
- 'App\Subscriber\BugLabelNewIssueSubscriber'
9-
- 'App\Subscriber\AutoLabelPRFromContentSubscriber'
9+
- 'App\Subscriber\AutoLabelFromContentSubscriber'
1010
- 'App\Subscriber\MilestoneNewPRSubscriber'
1111
secret: '%env(SYMFONY_SECRET)%'
1212

@@ -17,7 +17,7 @@ parameters:
1717
- 'App\Subscriber\StatusChangeByReviewSubscriber'
1818
- 'App\Subscriber\NeedsReviewNewPRSubscriber'
1919
- 'App\Subscriber\BugLabelNewIssueSubscriber'
20-
- 'App\Subscriber\AutoLabelPRFromContentSubscriber'
20+
- 'App\Subscriber\AutoLabelFromContentSubscriber'
2121
- 'App\Subscriber\MilestoneNewPRSubscriber'
2222
secret: '%env(SYMFONY_DOCS_SECRET)%'
2323

@@ -29,7 +29,7 @@ parameters:
2929
- 'App\Subscriber\StatusChangeByReviewSubscriber'
3030
- 'App\Subscriber\NeedsReviewNewPRSubscriber'
3131
- 'App\Subscriber\BugLabelNewIssueSubscriber'
32-
- 'App\Subscriber\AutoLabelPRFromContentSubscriber'
32+
- 'App\Subscriber\AutoLabelFromContentSubscriber'
3333
- 'App\Subscriber\MilestoneNewPRSubscriber'
3434

3535
services:

config/services_test.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
autoconfigure: true
5+
6+
App\Issues\GitHub\CachedLabelsApi:
7+
class: App\Tests\Service\Issues\Github\FakedCachedLabelApi

src/Issues/GitHub/CachedLabelsApi.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use App\Repository\Repository;
66
use Github\Api\Issue\Labels;
7+
use Symfony\Contracts\Cache\CacheInterface;
8+
use Symfony\Contracts\Cache\ItemInterface;
79

810
/**
911
* @author Bernhard Schussek <bschussek@gmail.com>
@@ -16,13 +18,21 @@ class CachedLabelsApi
1618
private $labelsApi;
1719

1820
/**
21+
* In memory cache for specific issues.
22+
*
1923
* @var array<array-key, array<array-key, bool>>
2024
*/
2125
private $labelCache = [];
2226

23-
public function __construct(Labels $labelsApi)
27+
/**
28+
* @var CacheInterface
29+
*/
30+
private $cache;
31+
32+
public function __construct(Labels $labelsApi, CacheInterface $cache)
2433
{
2534
$this->labelsApi = $labelsApi;
35+
$this->cache = $cache;
2636
}
2737

2838
public function getIssueLabels($issueNumber, Repository $repository)
@@ -94,6 +104,21 @@ public function addIssueLabels($issueNumber, array $labels, Repository $reposito
94104
}
95105
}
96106

107+
/**
108+
* @return string[]
109+
*/
110+
public function getAllLabelsForRepository(Repository $repository): array
111+
{
112+
$key = 'labels'.sha1($repository->getFullName());
113+
114+
return $this->cache->get($key, function (ItemInterface $item) use ($repository) {
115+
$labels = $this->labelsApi->all($repository->getVendor(), $repository->getName());
116+
$item->expiresAfter(36000);
117+
118+
return array_column($labels, 'name');
119+
});
120+
}
121+
97122
private function getCacheKey($issueNumber, Repository $repository)
98123
{
99124
return sprintf('%s_%s_%s', $issueNumber, $repository->getVendor(), $repository->getName());

src/Subscriber/AutoLabelPRFromContentSubscriber.php renamed to src/Subscriber/AutoLabelFromContentSubscriber.php

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
use App\Event\GitHubEvent;
66
use App\GitHubEvents;
77
use App\Issues\GitHub\CachedLabelsApi;
8+
use App\Repository\Repository;
89
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
910

1011
/**
1112
* Looks at new pull requests and auto-labels based on text.
1213
*/
13-
class AutoLabelPRFromContentSubscriber implements EventSubscriberInterface
14+
class AutoLabelFromContentSubscriber implements EventSubscriberInterface
1415
{
1516
private $labelsApi;
1617

@@ -35,14 +36,15 @@ public function onPullRequest(GitHubEvent $event)
3536
if ('opened' !== $action = $data['action']) {
3637
return;
3738
}
39+
$repository = $event->getRepository();
3840

3941
$prNumber = $data['pull_request']['number'];
4042
$prTitle = $data['pull_request']['title'];
4143
$prBody = $data['pull_request']['body'];
4244
$prLabels = [];
4345

4446
// the PR title usually contains one or more labels
45-
foreach ($this->extractLabels($prTitle) as $label) {
47+
foreach ($this->extractLabels($prTitle, $repository) as $label) {
4648
$prLabels[] = $label;
4749
}
4850

@@ -60,26 +62,52 @@ public function onPullRequest(GitHubEvent $event)
6062
$prLabels[] = 'Deprecation';
6163
}
6264

63-
$this->labelsApi->addIssueLabels($prNumber, $prLabels, $event->getRepository());
65+
$this->labelsApi->addIssueLabels($prNumber, $prLabels, $repository);
6466

6567
$event->setResponseData([
6668
'pull_request' => $prNumber,
6769
'pr_labels' => $prLabels,
6870
]);
6971
}
7072

71-
private function extractLabels($prTitle)
73+
public function onIssue(GitHubEvent $event)
74+
{
75+
$data = $event->getData();
76+
if ('opened' !== $action = $data['action']) {
77+
return;
78+
}
79+
$repository = $event->getRepository();
80+
81+
$issueNumber = $data['issue']['number'];
82+
$prTitle = $data['issue']['title'];
83+
$labels = [];
84+
85+
// the issue title usually contains one or more labels
86+
foreach ($this->extractLabels($prTitle, $repository) as $label) {
87+
$labels[] = $label;
88+
}
89+
90+
$this->labelsApi->addIssueLabels($issueNumber, $labels, $repository);
91+
92+
$event->setResponseData([
93+
'issue' => $issueNumber,
94+
'issue_labels' => $labels,
95+
]);
96+
}
97+
98+
private function extractLabels($title, Repository $repository)
7299
{
73100
$labels = [];
74101

75102
// e.g. "[PropertyAccess] [RFC] [WIP] Allow custom methods on property accesses"
76-
if (preg_match_all('/\[(?P<labels>.+)\]/U', $prTitle, $matches)) {
103+
if (preg_match_all('/\[(?P<labels>.+)\]/U', $title, $matches)) {
77104
// creates a key=>val array, but the key is lowercased
105+
$allLabels = $this->labelsApi->getAllLabelsForRepository($repository);
78106
$validLabels = array_combine(
79107
array_map(function ($s) {
80108
return strtolower($s);
81-
}, $this->getValidLabels()),
82-
$this->getValidLabels()
109+
}, $allLabels),
110+
$allLabels
83111
);
84112

85113
foreach ($matches['labels'] as $label) {
@@ -95,34 +123,6 @@ private function extractLabels($prTitle)
95123
return $labels;
96124
}
97125

98-
/**
99-
* TODO: get valid labels from the repository via GitHub API.
100-
*/
101-
private function getValidLabels()
102-
{
103-
$realLabels = [
104-
'Asset', 'BC Break', 'BrowserKit', 'Bug', 'Cache', 'Config', 'Console',
105-
'Contracts', 'Critical', 'CssSelector', 'Debug', 'DebugBundle', 'DependencyInjection',
106-
'Deprecation', 'Doctrine', 'DoctrineBridge', 'DomCrawler', 'Dotenv',
107-
'DX', 'Enhancement', 'ErrorHandler', 'EventDispatcher', 'ExpressionLanguage',
108-
'Feature', 'Filesystem', 'Finder', 'Form', 'FrameworkBundle', 'Hack Day',
109-
'HttpClient', 'HttpFoundation', 'HttpKernel', 'Inflector', 'Intl', 'Ldap',
110-
'Locale', 'Lock', 'Mailer', 'Messenger', 'Mime', 'MonologBridge', 'Notifier',
111-
'OptionsResolver', 'Performance', 'PhpUnitBridge', 'Process', 'PropertyAccess',
112-
'PropertyInfo', 'ProxyManagerBridge', 'RFC', 'Routing', 'Security',
113-
'SecurityBundle', 'Serializer', 'Stopwatch', 'String', 'Templating',
114-
'Translator', 'TwigBridge', 'TwigBundle', 'Uid', 'Validator', 'VarDumper',
115-
'VarExporter', 'WebLink', 'WebProfilerBundle', 'WebServerBundle', 'Workflow',
116-
'Yaml',
117-
];
118-
119-
return array_merge(
120-
$realLabels,
121-
// also consider the "aliases" as valid, so they are used
122-
array_keys(self::$labelAliases)
123-
);
124-
}
125-
126126
/**
127127
* It fixes common misspellings and aliases commonly used for label names
128128
* (e.g. DI -> DependencyInjection).
@@ -142,6 +142,7 @@ public static function getSubscribedEvents()
142142
{
143143
return [
144144
GitHubEvents::PULL_REQUEST => 'onPullRequest',
145+
GitHubEvents::ISSUES => 'onIssue',
145146
];
146147
}
147148
}

tests/Issues/GitHub/CachedLabelsApiTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Github\Api\Issue\Labels;
88
use PHPUnit\Framework\MockObject\MockObject;
99
use PHPUnit\Framework\TestCase;
10+
use Symfony\Component\Cache\Adapter\NullAdapter;
1011

1112
/**
1213
* @author Bernhard Schussek <bschussek@gmail.com>
@@ -34,7 +35,7 @@ protected function setUp()
3435
$this->backendApi = $this->getMockBuilder(Labels::class)
3536
->disableOriginalConstructor()
3637
->getMock();
37-
$this->api = new CachedLabelsApi($this->backendApi);
38+
$this->api = new CachedLabelsApi($this->backendApi, new NullAdapter());
3839
$this->repository = new Repository(
3940
self::USER_NAME,
4041
self::REPO_NAME,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Tests\Service\Issues\Github;
6+
7+
use App\Issues\GitHub\CachedLabelsApi;
8+
use App\Repository\Repository;
9+
10+
class FakedCachedLabelApi extends CachedLabelsApi
11+
{
12+
public function getAllLabelsForRepository(Repository $repository): array
13+
{
14+
return [
15+
'Asset', 'BC Break', 'BrowserKit', 'Bug', 'Cache', 'Config', 'Console',
16+
'Contracts', 'Critical', 'CssSelector', 'Debug', 'DebugBundle', 'DependencyInjection',
17+
'Deprecation', 'Doctrine', 'DoctrineBridge', 'DomCrawler', 'Dotenv',
18+
'DX', 'Enhancement', 'ErrorHandler', 'EventDispatcher', 'ExpressionLanguage',
19+
'Feature', 'Filesystem', 'Finder', 'Form', 'FrameworkBundle', 'Hack Day',
20+
'HttpClient', 'HttpFoundation', 'HttpKernel', 'Inflector', 'Intl', 'Ldap',
21+
'Locale', 'Lock', 'Mailer', 'Messenger', 'Mime', 'MonologBridge', 'Notifier',
22+
'OptionsResolver', 'Performance', 'PhpUnitBridge', 'Process', 'PropertyAccess',
23+
'PropertyInfo', 'ProxyManagerBridge', 'RFC', 'Routing', 'Security',
24+
'SecurityBundle', 'Serializer', 'Stopwatch', 'String', 'Templating',
25+
'Translator', 'TwigBridge', 'TwigBundle', 'Uid', 'Validator', 'VarDumper',
26+
'VarExporter', 'WebLink', 'WebProfilerBundle', 'WebServerBundle', 'Workflow',
27+
'Yaml',
28+
];
29+
}
30+
}

tests/Subscriber/AutoLabelPRFromContentSubscriberTest.php renamed to tests/Subscriber/AutoLabelFromContentSubscriberTest.php

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
use App\Event\GitHubEvent;
66
use App\GitHubEvents;
7-
use App\Issues\GitHub\CachedLabelsApi;
87
use App\Repository\Repository;
9-
use App\Subscriber\AutoLabelPRFromContentSubscriber;
8+
use App\Subscriber\AutoLabelFromContentSubscriber;
9+
use App\Tests\Service\Issues\Github\FakedCachedLabelApi;
1010
use PHPUnit\Framework\TestCase;
1111
use Symfony\Component\EventDispatcher\EventDispatcher;
1212

13-
class AutoLabelPRFromContentSubscriberTest extends TestCase
13+
class AutoLabelFromContentSubscriberTest extends TestCase
1414
{
1515
private $autoLabelSubscriber;
1616

@@ -25,20 +25,46 @@ class AutoLabelPRFromContentSubscriberTest extends TestCase
2525

2626
protected function setUp()
2727
{
28-
$this->labelsApi = $this->getMockBuilder(CachedLabelsApi::class)
28+
$this->labelsApi = $this->getMockBuilder(FakedCachedLabelApi::class)
2929
->disableOriginalConstructor()
30+
->setMethods(['addIssueLabels'])
3031
->getMock();
31-
$this->autoLabelSubscriber = new AutoLabelPRFromContentSubscriber($this->labelsApi);
32+
$this->autoLabelSubscriber = new AutoLabelFromContentSubscriber($this->labelsApi);
3233
$this->repository = new Repository('weaverryan', 'symfony', null);
3334

3435
$this->dispatcher = new EventDispatcher();
3536
$this->dispatcher->addSubscriber($this->autoLabelSubscriber);
3637
}
3738

39+
public function testAutoLabelIssue()
40+
{
41+
$this->labelsApi->expects($this->once())
42+
->method('addIssueLabels')
43+
->with(1234, ['Messenger'], $this->repository)
44+
->willReturn(null);
45+
46+
$event = new GitHubEvent([
47+
'action' => 'opened',
48+
'issue' => [
49+
'number' => 1234,
50+
'title' => '[Messenger] Foobar',
51+
'body' => 'Some content',
52+
],
53+
], $this->repository);
54+
55+
$this->dispatcher->dispatch($event, GitHubEvents::ISSUES);
56+
57+
$responseData = $event->getResponseData();
58+
59+
$this->assertCount(2, $responseData);
60+
$this->assertSame(1234, $responseData['issue']);
61+
$this->assertSame(['Messenger'], $responseData['issue_labels']);
62+
}
63+
3864
/**
3965
* @dataProvider getPRTests
4066
*/
41-
public function testAutoLabel($prTitle, $prBody, array $expectedNewLabels)
67+
public function testAutoLabelPR($prTitle, $prBody, array $expectedNewLabels)
4268
{
4369
$this->labelsApi->expects($this->once())
4470
->method('addIssueLabels')

0 commit comments

Comments
 (0)