Skip to content

Commit 35b2e6f

Browse files
committed
Add clarifications to the team problemset page.
Fixes #3009
1 parent f346616 commit 35b2e6f

File tree

8 files changed

+210
-29
lines changed

8 files changed

+210
-29
lines changed

webapp/src/Controller/Team/ClarificationController.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,61 @@ public function __construct(
4545
parent::__construct($em, $eventLogService, $dj, $kernel);
4646
}
4747

48+
#[Route(path: '/clarifications/by-problem/{probId<\d+>}', name: 'team_clarification_by_prob')]
49+
public function viewByProblemAction(Request $request, int $probId): Response
50+
{
51+
$user = $this->dj->getUser();
52+
$team = $user->getTeam();
53+
$teamId = $team->getTeamid();
54+
$contest = $this->dj->getCurrentContest($teamId);
55+
56+
$problem = $this->em->getRepository(Problem::class)->find($probId);
57+
if ($problem === null) {
58+
throw new NotFoundHttpException(sprintf('Problem %d not found', $probId));
59+
}
60+
$contestProblem = $problem->getContestProblems();
61+
$foundProblemInContest = false;
62+
foreach ($contestProblem as $cp) {
63+
if ($cp->getContest()->getCid() === $contest->getCid()) {
64+
$foundProblemInContest = true;
65+
break;
66+
}
67+
}
68+
if (!$foundProblemInContest) {
69+
throw new NotFoundHttpException(sprintf('Problem %d not in current contest', $probId));
70+
}
71+
72+
/** @var Clarification[] $clarifications */
73+
$clarifications = $this->em->createQueryBuilder()
74+
->from(Clarification::class, 'c')
75+
->leftJoin('c.problem', 'p')
76+
->leftJoin('c.sender', 's')
77+
->leftJoin('c.recipient', 'r')
78+
->select('c', 'p')
79+
->andWhere('c.contest = :contest')
80+
->andWhere('c.sender IS NULL')
81+
->andWhere('c.recipient = :team OR c.recipient IS NULL')
82+
->andWhere('c.problem = :problem')
83+
->setParameter('contest', $contest)
84+
->setParameter('team', $team)
85+
->setParameter('problem', $problem)
86+
->addOrderBy('c.submittime', 'DESC')
87+
->addOrderBy('c.clarid', 'DESC')
88+
->getQuery()
89+
->getResult();
90+
91+
$data = [
92+
'clarifications' => $clarifications,
93+
'team' => $team,
94+
'problem' => $problem,
95+
];
96+
if ($request->isXmlHttpRequest()) {
97+
return $this->render('team/clarifications_by_problem_modal.html.twig', $data);
98+
} else {
99+
return $this->render('team/clarifications_by_problem.html.twig', $data);
100+
}
101+
}
102+
48103
/**
49104
* @throws NonUniqueResultException
50105
*/

webapp/src/Service/DOMJudgeService.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,7 @@ public function getTwigDataForProblemsAction(
10281028

10291029
$problems = [];
10301030
$samples = [];
1031+
$clars = [];
10311032
if ($contest && ($forJury || $contest->getFreezeData()->started())) {
10321033
$problems = $this->em->createQueryBuilder()
10331034
->from(ContestProblem::class, 'cp')
@@ -1057,6 +1058,27 @@ public function getTwigDataForProblemsAction(
10571058
foreach ($samplesData as $sample) {
10581059
$samples[$sample['probid']] = $sample['numsamples'];
10591060
}
1061+
1062+
$raw_clars = $this->em->createQueryBuilder()
1063+
->from(Clarification::class, 'clar')
1064+
->select('clar')
1065+
->andWhere('clar.contest = :cid')
1066+
// Only clars associated with a problem.
1067+
->andWhere('clar.problem IS NOT NULL')
1068+
// Only clars send from the jury.
1069+
->andWhere('clar.sender IS NULL')
1070+
// Only clars send to all teams or just this team.
1071+
->andWhere('clar.recipient IS NULL OR clar.recipient = :teamid')
1072+
->setParameter('cid', $contest->getCid())
1073+
->setParameter('teamid', $teamId)
1074+
->orderBy('clar.submittime', 'DESC')
1075+
->getQuery()
1076+
->getResult();
1077+
1078+
// Group clarifications by problem id.
1079+
foreach ($raw_clars as $clar) {
1080+
$clars[$clar->getProblem()->getProbid()][] = $clar;
1081+
}
10601082
}
10611083

10621084
$data = [
@@ -1065,6 +1087,8 @@ public function getTwigDataForProblemsAction(
10651087
'showLimits' => $showLimits,
10661088
'defaultMemoryLimit' => $defaultMemoryLimit,
10671089
'timeFactorDiffers' => $timeFactorDiffers,
1090+
'clarifications' => $clars,
1091+
'team' => $teamId ? $this->em->getRepository(Team::class)->find($teamId) : null,
10681092
];
10691093

10701094
if ($contest && $this->config->get('show_public_stats')) {

webapp/templates/partials/problem_list.html.twig

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,6 @@
109109
{% endif %}
110110

111111
<div class="text-center">
112-
{% if problem.problem.problemstatementType is not empty %}
113-
<a class="btn btn-secondary" role="button"
114-
href="{{ path(problem_statement_path, {'probId': problem.probid}) }}">
115-
<i class="fas fa-file-{{ problem.problem.problemstatementType }}"></i>
116-
statement
117-
</a>
118-
{% endif %}
119-
120-
{% if numsamples > 0 %}
121-
<a class="btn btn-secondary" role="button"
122-
href="{{ path(problem_sample_zip_path, {'probId': problem.probid}) }}">
123-
<i class="fas fa-file-archive"></i> samples
124-
</a>
125-
{% endif %}
126-
127112
{% if show_submit_button | default(false) %}
128113
{% if is_granted('ROLE_JURY') or (current_team_contest is not null and current_team_contest.freezeData.started) %}
129114
<a class="justify-content-center" data-ajax-modal data-ajax-modal-after="initSubmitModal" href="{{ path('team_submit', {problem: problem.probid}) }}">
@@ -139,6 +124,49 @@
139124
</a>
140125
{% endif %}
141126
{% endif %}
127+
128+
{% set clarificationsCount = 0 %}
129+
{% set unseenClarificationCount = 0 %}
130+
{% if clarifications[problem.probid] is defined %}
131+
{% set clarificationsCount = clarifications[problem.probid] | length %}
132+
{% for clar in clarifications[problem.probid] %}
133+
{% if team.unreadClarifications.contains(clar) %}
134+
{% set unseenClarificationCount = unseenClarificationCount + 1 %}
135+
{% endif %}
136+
{% endfor %}
137+
{% endif %}
138+
{% if clarificationsCount > 0 %}
139+
<a data-ajax-modal class="btn btn-secondary" role="button"
140+
href="{{ path('team_clarification_by_prob', {'probId': problem.probid}) }}">
141+
<i class="fas fa-question-circle"></i>
142+
{% if clarificationsCount > 0 %}
143+
{% set badgeClass = 'text-bg-info' %}
144+
{% if unseenClarificationCount > 0 %}
145+
{% set badgeClass = 'text-bg-danger' %}
146+
{% endif %}
147+
<span class="badge {{ badgeClass }}">{{ clarificationsCount }}</span>
148+
{% endif %}
149+
clarifications
150+
</a>
151+
{% endif %}
152+
153+
<div style="margin-top: 10px;">
154+
</div>
155+
156+
{% if problem.problem.problemstatementType is not empty %}
157+
<a class="btn btn-secondary" role="button"
158+
href="{{ path(problem_statement_path, {'probId': problem.probid}) }}">
159+
<i class="fas fa-file-{{ problem.problem.problemstatementType }}"></i>
160+
statement
161+
</a>
162+
{% endif %}
163+
164+
{% if numsamples > 0 %}
165+
<a class="btn btn-secondary" role="button"
166+
href="{{ path(problem_sample_zip_path, {'probId': problem.probid}) }}">
167+
<i class="fas fa-file-archive"></i> samples
168+
</a>
169+
{% endif %}
142170
</div>
143171

144172
{% if problem.problem.attachments | length > 0 %}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{% extends 'team/base.html.twig' %}
2+
3+
{% block title %}View clarifications for problem {{ problem.name }}{% endblock %}
4+
5+
{% block extrahead %}
6+
{{ parent() }}
7+
<style>
8+
.data-table td a, .data-table td a:hover {
9+
display: block;
10+
text-decoration: none;
11+
color: inherit;
12+
padding: 3px 5px;
13+
}
14+
15+
.data-table tr {
16+
border-bottom: 1px solid silver;
17+
}
18+
19+
.data-table tr:hover {
20+
background: #ffffcc !important;
21+
}
22+
</style>
23+
{% endblock %}
24+
25+
{% block content %}
26+
<h2 class="text-center mt-4">Clarifications for {{ problem | problemBadgeForContest }} {{ problem.name }}</h2>
27+
28+
{% if clarifications is empty %}
29+
<p class="nodata">No clarifications.</p>
30+
{% else %}
31+
{% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications} %}
32+
{% endif %}
33+
{% endblock %}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{% extends "partials/modal.html.twig" %}
2+
3+
{% block title %}View clarifications for problem {{ problem | problemBadgeForContest }} {{ problem.name }}{% endblock %}
4+
5+
{% block extrahead %}
6+
{{ parent() }}
7+
<style>
8+
.data-table td a, .data-table td a:hover {
9+
display: block;
10+
text-decoration: none;
11+
color: inherit;
12+
padding: 3px 5px;
13+
}
14+
15+
.data-table tr {
16+
border-bottom: 1px solid silver;
17+
}
18+
19+
.data-table tr:hover {
20+
background: #ffffcc !important;
21+
}
22+
</style>
23+
{% endblock %}
24+
25+
{% block content %}
26+
{% if clarifications is empty %}
27+
<p class="nodata">No clarifications.</p>
28+
{% else %}
29+
{% include 'team/partials/clarification_list.html.twig' with {clarifications: clarifications, subject: false} %}
30+
{% endif %}
31+
{% endblock %}

webapp/templates/team/partials/clarification_list.html.twig

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
{% set includeSubject = true %}
2+
{% if subject is defined and not subject %}
3+
{% set includeSubject = false %}
4+
{% endif %}
5+
16
<div class="table-wrapper">
27
<table class="data-table table table-striped table-hover table-sm" style="width:100%;">
38
<thead class="thead-light">
49
<tr>
510
<th scope="col">time</th>
611
<th scope="col">from</th>
712
<th scope="col">to</th>
8-
<th scope="col">subject</th>
13+
{% if includeSubject %}
14+
<th scope="col">subject</th>
15+
{% endif %}
916
<th scope="col">text</th>
1017
</tr>
1118
</thead>
@@ -41,17 +48,19 @@
4148
{{ recipient | u.truncate(teamname_max_length, '') }}
4249
</a>
4350
</td>
44-
<td>
45-
<a data-ajax-modal data-ajax-modal-after="markSeen" href="{{ link }}">
46-
{%- if clarification.problem -%}
47-
problem {{ clarification.contestProblem | problemBadge -}}
48-
{%- elseif clarification.category -%}
49-
{{- categories[clarification.category]|default('general') -}}
50-
{%- else -%}
51-
general
52-
{%- endif -%}
53-
</a>
54-
</td>
51+
{% if includeSubject %}
52+
<td>
53+
<a data-ajax-modal data-ajax-modal-after="markSeen" href="{{ link }}">
54+
{%- if clarification.problem -%}
55+
problem {{ clarification.contestProblem | problemBadge -}}
56+
{%- elseif clarification.category -%}
57+
{{- categories[clarification.category]|default('general') -}}
58+
{%- else -%}
59+
general
60+
{%- endif -%}
61+
</a>
62+
</td>
63+
{% endif %}
5564

5665
<td>
5766
<a data-ajax-modal data-ajax-modal-after="markSeen" href="{{ link }}">

webapp/templates/team/problems.html.twig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
problem_statement_path: 'team_problem_statement',
1111
problem_attachment_path: 'team_problem_attachment',
1212
problem_sample_zip_path: 'team_problem_sample_zip',
13-
show_submit_button: true
13+
show_submit_button: true,
14+
team: team
1415
} %}
1516
{% endblock %}

webapp/tests/Unit/Controller/Team/ProblemControllerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public function testSamples(): void
133133
$cardBodies->eq(2)->filter('.list-group .list-group-item')->count());
134134

135135
// Check the link to download all samples.
136-
$link = $cardBodies->eq(1)->filter('a')->eq(1);
136+
$link = $cardBodies->eq(1)->filter('a')->eq(2);
137137
self::assertSame('samples', $link->text(null, true));
138138
self::assertSame(sprintf('/team/%d/samples.zip',
139139
$problem->getProbid()),

0 commit comments

Comments
 (0)