Skip to content

Commit d8f00a1

Browse files
committed
fix #37: implement bug selection page
1 parent dd30a42 commit d8f00a1

11 files changed

+318
-36
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"doctrine/orm": "*",
44
"doctrine/annotations": "*",
55
"knplabs/github-api": "*",
6+
"mpratt/embera": "*",
67
"nyholm/psr7": "*",
78
"symfony/cache": "*",
89
"symfony/http-client": "*",

cron.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,10 @@ function run_repository() {
285285
}
286286

287287
if (!$processed) {
288-
$patch = Patch::factory($group, $pr->branchURL(), PATCH_BUGFIX, '',
289-
'Automatically generated', null,
290-
/*ignore_errors=*/true);
288+
$patch = Patch::factory($group, $pr->branchURL(), PATCH_BUGFIX,
289+
"This patch entry was automatically generated.\n".
290+
"The PR was opened without permission!",
291+
$user, /*ignore_errors=*/true);
291292
$patch->setPR($pr);
292293
$patch->status = PATCH_PR_OPEN_ILLEGAL;
293294
$group->patches->add($patch);

db.php

+35
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,41 @@ function db_fetch_patch_id($id) : ?Patch {
214214
return $entityManager->find('Patch', $id);
215215
}
216216

217+
function db_fetch_bug_issue(int $year, string $issue_url) : ?SelectedBug {
218+
global $entityManager;
219+
return $entityManager->createQueryBuilder()
220+
->from('SelectedBug', 'b')
221+
->select('b')
222+
->where('b.year = :year')
223+
->andWhere('b.issue_url = :url')
224+
->setParameter('year', $year)
225+
->setParameter('url', $issue_url)
226+
->getQuery()
227+
->getOneOrNullResult();
228+
}
229+
230+
function db_fetch_bug_user(int $year, User $user) : ?SelectedBug {
231+
global $entityManager;
232+
return $entityManager->createQueryBuilder()
233+
->from('SelectedBug', 'b')
234+
->select('b')
235+
->where('b.year = :year')
236+
->andWhere('b.user = :user')
237+
->setParameter('year', $year)
238+
->setParameter('user', $user->id)
239+
->getQuery()
240+
->getOneOrNullResult();
241+
}
242+
243+
function db_fetch_bugs_group(ProjGroup $group) : array {
244+
$bugs = [];
245+
foreach ($group->students as $student) {
246+
if ($bug = db_fetch_bug_user($group->year, $student))
247+
$bugs[] = $bug;
248+
}
249+
return $bugs;
250+
}
251+
217252
function db_get_merged_patch_stats() {
218253
global $entityManager;
219254
return $entityManager->createQueryBuilder()

entities/Deadline.php

+9
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,32 @@ class Deadline
1515
/** @Column */
1616
public DateTimeImmutable $proj_proposal;
1717

18+
/** @Column */
19+
public DateTimeImmutable $bug_selection;
20+
1821
/** @Column */
1922
public DateTimeImmutable $patch_submission;
2023

2124
public function __construct($year) {
2225
$this->year = $year;
2326
$this->proj_proposal = new DateTimeImmutable();
27+
$this->bug_selection = new DateTimeImmutable();
2428
$this->patch_submission = new DateTimeImmutable();
2529
}
2630

2731
public function isProjProposalActive() {
2832
return new DateTimeImmutable() <= $this->proj_proposal;
2933
}
3034

35+
public function isBugSelectionActive() {
36+
return new DateTimeImmutable() <= $this->bug_selection;
37+
}
38+
3139
public function isPatchSubmissionActive() {
3240
return new DateTimeImmutable() <= $this->patch_submission;
3341
}
3442

3543
public function set_proj_proposal($time) { $this->proj_proposal = new DateTimeImmutable($time); }
44+
public function set_bug_selection($time) { $this->bug_selection = new DateTimeImmutable($time); }
3645
public function set_patch_submission($time) { $this->patch_submission = new DateTimeImmutable($time); }
3746
}

entities/Patch.php

+20-22
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
/** @Entity
3939
* @Table(name="Patch",
4040
* uniqueConstraints={
41-
* @UniqueConstraint(name="unique_repo_per_group", columns={"group_id", "repo_branch"}),
41+
* @UniqueConstraint(name="unique_branch_per_group", columns={"group_id", "repo_branch"}),
4242
* }
4343
* )
4444
* @InheritanceType("SINGLE_TABLE")
@@ -61,9 +61,6 @@ abstract class Patch
6161
/** @Column */
6262
public int $type;
6363

64-
/** @Column */
65-
public string $issue_url = 'https://...';
66-
6764
/** @OneToMany(targetEntity="PatchComment", mappedBy="patch", cascade={"persist"})
6865
* @OrderBy({"id" = "ASC"})
6966
*/
@@ -90,17 +87,15 @@ abstract class Patch
9087
public int $files_modified;
9188

9289
static function factory(ProjGroup $group, string $url, $type,
93-
string $issue_url, string $description,
94-
?User $submitter,
90+
string $description, User $submitter,
9591
bool $ignore_errors = false) : Patch {
9692
$repo = $group->getRepository();
9793
if (!$repo)
9894
throw new ValidationException('Group has no repository yet');
9995

100-
$p = GitHub\GitHubPatch::construct($url, $repo);
101-
$p->group = $group;
102-
$p->type = (int)$type;
103-
$p->issue_url = check_url($issue_url);
96+
$p = GitHub\GitHubPatch::construct($url, $repo);
97+
$p->group = $group;
98+
$p->type = (int)$type;
10499

105100
try {
106101
$p->updateStats();
@@ -135,9 +130,6 @@ static function factory(ProjGroup $group, string $url, $type,
135130
foreach ($group->patches as $old_patch) {
136131
if ($p->origin() == $old_patch->origin())
137132
throw new ValidationException('Duplicated patch');
138-
if ($p->issue_url && $p->issue_url == $old_patch->issue_url)
139-
throw new ValidationException(
140-
'There is already a patch for the same issue');
141133
}
142134

143135
foreach ($commits as $commit) {
@@ -181,8 +173,6 @@ static function factory(ProjGroup $group, string $url, $type,
181173
}
182174

183175
if ($p->type == PATCH_BUGFIX) {
184-
if (!$p->issue_url)
185-
throw new ValidationException('Issue field empty');
186176
if (count($commits) != 1)
187177
throw new ValidationException('Only 1 commit allowed');
188178

@@ -191,10 +181,11 @@ static function factory(ProjGroup $group, string $url, $type,
191181
"Commit message doesn't reference the fixed issue properly:\n" .
192182
$commits[0]['message']);
193183

194-
if (!strstr($p->issue_url, $m[1]))
184+
$issue_url = $p->getIssueURL();
185+
if (!strstr($issue_url, $m[1]))
195186
throw new ValidationException(
196187
"Referenced issue #$m[1] doesn't match the specified issue URL: " .
197-
$p->issue_url);
188+
$issue_url);
198189
}
199190
} catch (ValidationException $ex) {
200191
if (!$ignore_errors)
@@ -287,6 +278,16 @@ public function getPRURL() {
287278
return $pr ? $pr->url() : null;
288279
}
289280

281+
public function getIssueURL() : string {
282+
if ($this->type != PATCH_BUGFIX)
283+
return '';
284+
285+
$bug = db_fetch_bug_user($this->group->year, $this->getSubmitter());
286+
if ($bug === null)
287+
throw new ValidationException('Patch does not have a bug associated');
288+
return $bug->issue_url;
289+
}
290+
290291
/// returns (login, name, email)*
291292
public function allAuthors() {
292293
$authors = [];
@@ -335,13 +336,12 @@ public function wasMerged() {
335336
$this->status == PATCH_MERGED_ILLEGAL;
336337
}
337338

338-
public function getSubmitter() : ?User {
339+
public function getSubmitter() : User {
339340
return $this->comments[0]->user;
340341
}
341342

342343
public function getSubmitterName() : string {
343-
$user = $this->getSubmitter();
344-
return $user ? $user->shortName() : '(Bot)';
344+
return $this->getSubmitter()->shortName();
345345
}
346346

347347
public function getHashes() {
@@ -375,6 +375,4 @@ public function set_type($type) {
375375
throw new ValidationException('invalid type');
376376
$this->type = $type;
377377
}
378-
379-
public function set_issue_url($url) { $this->issue_url = check_url($url); }
380378
}

entities/SelectedBug.php

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
// Copyright (c) 2022-present Instituto Superior Técnico.
3+
// Distributed under the MIT license that can be found in the LICENSE file.
4+
5+
use Doctrine\ORM\Mapping\Column;
6+
use Doctrine\ORM\Mapping\Entity;
7+
use Doctrine\ORM\Mapping\GeneratedValue;
8+
use Doctrine\ORM\Mapping\Id;
9+
use Doctrine\ORM\Mapping\ManyToOne;
10+
use Doctrine\ORM\Mapping\Table;
11+
use Doctrine\ORM\Mapping\UniqueConstraint;
12+
require 'video.php';
13+
14+
/** @Entity
15+
* @Table(name="SelectedBug",
16+
* uniqueConstraints={
17+
* @UniqueConstraint(name="unique_bug_issue", columns={"year", "issue_url"}),
18+
* @UniqueConstraint(name="unique_bug_user", columns={"year", "user_id"}),
19+
* }
20+
* )
21+
*/
22+
class SelectedBug
23+
{
24+
/** @Id @Column @GeneratedValue */
25+
public int $id;
26+
27+
/** @Column */
28+
public int $year;
29+
30+
/** @Column */
31+
public string $issue_url = 'https://...';
32+
33+
/** @Column */
34+
public string $repro_url = 'https://...';
35+
36+
/** @Column(length=4096) */
37+
public string $description;
38+
39+
/** @ManyToOne */
40+
public User $user;
41+
42+
static function factory(ProjGroup $group, User $user, string $description,
43+
string $issue_url, string $repro_url) {
44+
$bug = new SelectedBug();
45+
$bug->year = $group->year;
46+
$bug->set_issue_url($issue_url);
47+
$bug->set_repro_url($repro_url);
48+
$bug->description = trim($description);
49+
$bug->user = $user;
50+
return $bug;
51+
}
52+
53+
function getVideoHTML() {
54+
return
55+
$this->repro_url
56+
? get_video_info($this->repro_url)['html_pre_responsive'] : '';
57+
}
58+
59+
function set_issue_url(string $url) {
60+
if (!$url) {
61+
throw new ValidationException('Issue URL is required');
62+
}
63+
if ($url != $this->issue_url &&
64+
db_fetch_bug_issue($this->year, $url) !== null) {
65+
throw new ValidationException(
66+
'This bug has been selected by another student already');
67+
}
68+
$this->issue_url = check_url($url);
69+
}
70+
71+
function set_repro_url(string $url) {
72+
if ($url && !get_video_info($url)) {
73+
throw new ValidationException('Repro URL is not recognized as a video');
74+
}
75+
$this->repro_url = check_url($url);
76+
}
77+
}

0 commit comments

Comments
 (0)