Skip to content

Commit ca678dd

Browse files
Show list of submissions on public and team scoreboards when clicking on a cell
Fixes DOMjudge#2427
1 parent 44a03e8 commit ca678dd

9 files changed

+181
-2
lines changed

webapp/public/style_domjudge.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,14 @@ tr.ignore td, td.ignore, span.ignore {
634634
min-width: 2em;
635635
}
636636

637+
h5 .problem-badge {
638+
font-size: 1rem;
639+
}
640+
641+
h1 .problem-badge {
642+
font-size: 2rem;
643+
}
644+
637645
.tooltip .tooltip-inner {
638646
max-width: 500px;
639647
}

webapp/src/Controller/PublicController.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Controller;
44

5+
use App\DataTransferObject\SubmissionRestriction;
56
use App\Entity\Contest;
67
use App\Entity\ContestProblem;
78
use App\Entity\Team;
@@ -11,6 +12,7 @@
1112
use App\Service\EventLogService;
1213
use App\Service\ScoreboardService;
1314
use App\Service\StatisticsService;
15+
use App\Service\SubmissionService;
1416
use Doctrine\ORM\EntityManagerInterface;
1517
use Doctrine\ORM\NonUniqueResultException;
1618
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -33,6 +35,7 @@ public function __construct(
3335
protected readonly ConfigurationService $config,
3436
protected readonly ScoreboardService $scoreboardService,
3537
protected readonly StatisticsService $stats,
38+
protected readonly SubmissionService $submissionService,
3639
EntityManagerInterface $em,
3740
EventLogService $eventLog,
3841
KernelInterface $kernel,
@@ -79,6 +82,18 @@ public function scoreboardAction(
7982

8083
if ($static) {
8184
$data['hide_menu'] = true;
85+
$submissions = $this->submissionService->getSubmissionList(
86+
[$contest->getCid() => $contest],
87+
new SubmissionRestriction(valid: true),
88+
paginated: false
89+
)[0];
90+
91+
$submissionsPerTeamAndProblem = [];
92+
foreach ($submissions as $submission) {
93+
$submissionsPerTeamAndProblem[$submission->getTeam()->getTeamid()][$submission->getProblem()->getProbid()][] = $submission;
94+
}
95+
$data['submissionsPerTeamAndProblem'] = $submissionsPerTeamAndProblem;
96+
$data['verificationRequired'] = $this->config->get('verification_required');
8297
}
8398

8499
$data['current_contest'] = $contest;
@@ -267,4 +282,54 @@ protected function getBinaryFile(int $probId, callable $response): StreamedRespo
267282

268283
return $response($probId, $contest, $contestProblem);
269284
}
285+
286+
#[Route(path: '/submissions/team/{teamId<\d+>}/problem/{problemId<\d+>}', name: 'public_submissions')]
287+
public function submissionsAction(Request $request, int $teamId, int $problemId): Response
288+
{
289+
$contest = $this->dj->getCurrentContest(onlyPublic: true);
290+
291+
if (!$contest) {
292+
throw $this->createNotFoundException('No active contest found');
293+
}
294+
295+
/** @var Team|null $team */
296+
$team = $this->em->getRepository(Team::class)->find($teamId);
297+
if ($team && $team->getCategory() && !$team->getCategory()->getVisible()) {
298+
$team = null;
299+
}
300+
301+
if (!$team) {
302+
throw $this->createNotFoundException('Team not found');
303+
}
304+
305+
/** @var ContestProblem|null $problem */
306+
$problem = $this->em->getRepository(ContestProblem::class)->find([
307+
'problem' => $problemId,
308+
'contest' => $contest,
309+
]);
310+
311+
if (!$problem) {
312+
throw $this->createNotFoundException('Problem not found');
313+
}
314+
315+
$submissions = $this->submissionService->getSubmissionList(
316+
[$contest->getCid() => $contest],
317+
new SubmissionRestriction(teamId: $teamId, problemId: $problemId, valid: true),
318+
paginated: false
319+
)[0];
320+
321+
$data = [
322+
'contest' => $contest,
323+
'problem' => $problem,
324+
'team' => $team,
325+
'submissions' => $submissions,
326+
'verificationRequired' => $this->config->get('verification_required'),
327+
];
328+
329+
if ($request->isXmlHttpRequest()) {
330+
return $this->render('public/team_submissions_modal.html.twig', $data);
331+
}
332+
333+
return $this->render('public/team_submissions.html.twig', $data);
334+
}
270335
}

webapp/src/Controller/Team/SubmissionController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,10 @@ public function downloadAction(int $submitId): Response
248248

249249
return $this->submissionService->getSubmissionZipResponse($submission);
250250
}
251+
252+
#[Route(path: '/submissions/team/{teamId<\d+>}/problem/{problemId<\d+>}', name: 'team_submissions')]
253+
public function listAction(int $probId): Response
254+
{
255+
256+
}
251257
}

webapp/src/DataTransferObject/SubmissionRestriction.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ public function __construct(
6969
public ?bool $externallyJudged = null,
7070
public ?bool $externallyVerified = null,
7171
public ?bool $withExternalId = null,
72+
public ?bool $valid = null,
7273
) {}
7374
}

webapp/src/Service/SubmissionService.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@ public function getSubmissionList(
305305
->setParameter('results', $restrictions->results);
306306
}
307307

308+
if (isset($restrictions->valid)) {
309+
$queryBuilder
310+
->andWhere('s.valid = :valid')
311+
->setParameter('valid', $restrictions->valid);
312+
}
313+
308314
if ($this->dj->shadowMode()) {
309315
// When we are shadow, also load the external results
310316
$queryBuilder

webapp/templates/partials/scoreboard_table.html.twig

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,21 @@
286286
{% endif %}
287287
{% endif %}
288288
289-
{% set link = null %}
289+
{% set extra = null %}
290290
{% if jury %}
291291
{% set restrict = {problemId: problem.probid} %}
292292
{% set link = path('jury_team', {teamId: score.team.teamid, restrict: restrict}) %}
293+
{% elseif static %}
294+
{% set link = '#' %}
295+
{% set extra = 'data-bs-toggle="modal" data-bs-target="#team-submissions-modal-' ~ score.team.teamid ~ '-' ~ problem.probid ~ '"' %}
296+
{% else %}
297+
{% set link = path('public_submissions', {teamId: score.team.teamid, problemId: problem.probid}) %}
298+
{% set extra = 'data-ajax-modal' %}
293299
{% endif %}
294300
295301
<td class="score_cell">
296302
{% if numSubmissions != '0' %}
297-
<a {% if link %}href="{{ link }}"{% endif %}>
303+
<a {% if link %}href="{{ link }}"{% endif %} {% if extra %}{{ extra | raw }}{% endif %}>
298304
<div class="{{ scoreCssClass }}">
299305
{% if matrixItem.isCorrect %}{{ time }}{% else %}&nbsp;{% endif %}
300306
<span>
@@ -503,6 +509,16 @@
503509
{% include 'partials/team.html.twig' with {size: 6, team: score.team} %}
504510
{% endblock %}
505511
{% endembed %}
512+
{% for problem in problems %}
513+
{% embed 'partials/modal.html.twig' with {'modalId': 'team-submissions-modal-' ~ score.team.teamid ~ '-' ~ problem.probid} %}
514+
{% block title %}
515+
Submissions for team {{ score.team.effectiveName }} and problem {{ problem | problemBadge }} {{ problem.problem.name }}
516+
{% endblock %}
517+
{% block content %}
518+
{% include 'public/partials/submission_list.html.twig' with {size: 6, team: score.team, submissions: submissionsPerTeamAndProblem[score.team.teamid][problem.probid] ?? []} %}
519+
{% endblock %}
520+
{% endembed %}
521+
{% endfor %}
506522
{% endfor %}
507523
{% endif %}
508524
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{# Render a list of submissions for the scoreboard #}
2+
{# @var submission \App\Entity\Submission #}
3+
4+
{% if submissions is empty %}
5+
<div class="alert alert-warning">No submissions</div>
6+
{% else %}
7+
<table class="data-table table table-hover table-striped table-sm submissions-table">
8+
<thead class="thead-light">
9+
<tr>
10+
<th scope="col">time</th>
11+
<th scope="col">language</th>
12+
<th scope="col">result</th>
13+
{% if contest.getRuntimeAsScoreTiebreaker() %}
14+
<th scope="col">runtime</th>
15+
{% endif %}
16+
</tr>
17+
</thead>
18+
<tbody>
19+
{% for submission in submissions %}
20+
<tr>
21+
<td>
22+
{{ submission.submittime | printtime(null, submission.contest) }}
23+
</td>
24+
<td class="langid">
25+
{{ submission.language.langid }}
26+
</td>
27+
<td>
28+
{% if submission.submittime >= submission.contest.endtime %}
29+
{{ 'too-late' | printResult }}
30+
{% elseif submission.contest.freezetime and submission.submittime >= submission.contest.freezetime and not contest.freezeData.showFinal %}
31+
{{ '' | printResult }}
32+
{% else %}
33+
{% if submission.judgings.first is empty or submission.judgings.first.result is empty %}
34+
{{ '' | printResult }}
35+
{% elseif verificationRequired and not submission.judgings.first.verified %}
36+
{{ '' | printResult }}
37+
{% else %}
38+
{{ submission.judgings.first.result | printResult }}
39+
{% endif %}
40+
{% endif %}
41+
</td>
42+
{% if contest.getRuntimeAsScoreTiebreaker() %}
43+
<td>
44+
{% if link and submission.getResult() == 'correct' %}
45+
{{ "%0.3f s" | format(submission.judgings.first.getMaxRuntime()) }}
46+
{% else %}
47+
-
48+
{% endif %}
49+
</td>
50+
{% endif %}
51+
</tr>
52+
{% endfor %}
53+
</tbody>
54+
</table>
55+
{% endif %}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{% extends "public/base.html.twig" %}
2+
3+
{% block title %}
4+
{% if team is not empty %}Submissions for team {{ team.effectiveName }} and problem {{ problem.problem.name }} - {% endif %}{{ parent() }}
5+
{% endblock %}
6+
7+
{% block content %}
8+
<h1 class="mt-3">
9+
Submissions for team {{ team.effectiveName }} and problem {{ problem | problemBadge }} {{ problem.problem.name }}
10+
</h1>
11+
12+
{% include 'public/partials/submission_list.html.twig' %}
13+
{% endblock %}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "partials/modal.html.twig" %}
2+
3+
{% block title %}
4+
Submissions for team {{ team.effectiveName }} and problem {{ problem | problemBadge }} {{ problem.problem.name }}
5+
{% endblock %}
6+
7+
{% block content %}
8+
{% include 'public/partials/submission_list.html.twig' %}
9+
{% endblock %}

0 commit comments

Comments
 (0)