diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index a9c5f89168..dee407d4c3 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -634,6 +634,14 @@ tr.ignore td, td.ignore, span.ignore { min-width: 2em; } +h5 .problem-badge { + font-size: 1rem; +} + +h1 .problem-badge { + font-size: 2rem; +} + .tooltip .tooltip-inner { max-width: 500px; } diff --git a/webapp/src/Controller/PublicController.php b/webapp/src/Controller/PublicController.php index 9d368ad964..2f685e93e2 100644 --- a/webapp/src/Controller/PublicController.php +++ b/webapp/src/Controller/PublicController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\DataTransferObject\SubmissionRestriction; use App\Entity\Contest; use App\Entity\ContestProblem; use App\Entity\Team; @@ -11,6 +12,7 @@ use App\Service\EventLogService; use App\Service\ScoreboardService; use App\Service\StatisticsService; +use App\Service\SubmissionService; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -33,6 +35,7 @@ public function __construct( protected readonly ConfigurationService $config, protected readonly ScoreboardService $scoreboardService, protected readonly StatisticsService $stats, + protected readonly SubmissionService $submissionService, EntityManagerInterface $em, EventLogService $eventLog, KernelInterface $kernel, @@ -79,6 +82,18 @@ public function scoreboardAction( if ($static) { $data['hide_menu'] = true; + $submissions = $this->submissionService->getSubmissionList( + [$contest->getCid() => $contest], + new SubmissionRestriction(valid: true), + paginated: false + )[0]; + + $submissionsPerTeamAndProblem = []; + foreach ($submissions as $submission) { + $submissionsPerTeamAndProblem[$submission->getTeam()->getTeamid()][$submission->getProblem()->getProbid()][] = $submission; + } + $data['submissionsPerTeamAndProblem'] = $submissionsPerTeamAndProblem; + $data['verificationRequired'] = $this->config->get('verification_required'); } $data['current_contest'] = $contest; @@ -267,4 +282,53 @@ protected function getBinaryFile(int $probId, callable $response): StreamedRespo return $response($probId, $contest, $contestProblem); } + + #[Route(path: '/submissions/team/{teamId<\d+>}/problem/{problemId<\d+>}', name: 'public_submissions')] + public function submissionsAction(Request $request, int $teamId, int $problemId): Response + { + $contest = $this->dj->getCurrentContest(onlyPublic: true); + + if (!$contest) { + throw $this->createNotFoundException('No active contest found'); + } + + /** @var Team|null $team */ + $team = $this->em->getRepository(Team::class)->find($teamId); + if ($team && $team->getCategory() && !$team->getCategory()->getVisible()) { + $team = null; + } + + if (!$team) { + throw $this->createNotFoundException('Team not found.'); + } + + /** @var ContestProblem|null $problem */ + $problem = $this->em->getRepository(ContestProblem::class)->find([ + 'problem' => $problemId, + 'contest' => $contest, + ]); + + if (!$problem) { + throw $this->createNotFoundException('Problem not found'); + } + + $submissions = $this->submissionService->getSubmissionList( + [$contest->getCid() => $contest], + new SubmissionRestriction(teamId: $teamId, problemId: $problemId, valid: true), + paginated: false + )[0]; + + $data = [ + 'contest' => $contest, + 'problem' => $problem, + 'team' => $team, + 'submissions' => $submissions, + 'verificationRequired' => $this->config->get('verification_required'), + ]; + + if ($request->isXmlHttpRequest()) { + return $this->render('public/team_submissions_modal.html.twig', $data); + } + return $this->render('public/team_submissions.html.twig', $data); + } } diff --git a/webapp/src/DataTransferObject/SubmissionRestriction.php b/webapp/src/DataTransferObject/SubmissionRestriction.php index b62057d68a..c23f86bf4f 100644 --- a/webapp/src/DataTransferObject/SubmissionRestriction.php +++ b/webapp/src/DataTransferObject/SubmissionRestriction.php @@ -69,5 +69,6 @@ public function __construct( public ?bool $externallyJudged = null, public ?bool $externallyVerified = null, public ?bool $withExternalId = null, + public ?bool $valid = null, ) {} } diff --git a/webapp/src/Service/SubmissionService.php b/webapp/src/Service/SubmissionService.php index 52d95befa0..96a4452b52 100644 --- a/webapp/src/Service/SubmissionService.php +++ b/webapp/src/Service/SubmissionService.php @@ -305,6 +305,12 @@ public function getSubmissionList( ->setParameter('results', $restrictions->results); } + if (isset($restrictions->valid)) { + $queryBuilder + ->andWhere('s.valid = :valid') + ->setParameter('valid', $restrictions->valid); + } + if ($this->dj->shadowMode()) { // When we are shadow, also load the external results $queryBuilder diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index f66a0acff9..b76fd87439 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -513,8 +513,12 @@ public function displayTestcaseResults(array $testcases, bool $submissionDone, b return $results; } - public function printResult(?string $result, bool $valid = true, bool $jury = false): string - { + public function printResult( + ?string $result, + bool $valid = true, + bool $jury = false, + bool $onlyRejectedForIncorrect = false, + ): string { $result = strtolower($result ?? ''); switch ($result) { case 'too-late': @@ -539,6 +543,9 @@ public function printResult(?string $result, bool $valid = true, bool $jury = fa break; default: $style = 'sol_incorrect'; + if ($onlyRejectedForIncorrect) { + $result = 'rejected'; + } } return sprintf('%s', $valid ? $style : 'disabled', $result); diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index e3eaad7b22..2ee37a4eef 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -286,15 +286,21 @@ {% endif %} {% endif %} - {% set link = null %} + {% set extra = null %} {% if jury %} {% set restrict = {problemId: problem.probid} %} {% set link = path('jury_team', {teamId: score.team.teamid, restrict: restrict}) %} + {% elseif static %} + {% set link = '#' %} + {% set extra = 'data-bs-toggle="modal" data-bs-target="#team-submissions-modal-' ~ score.team.teamid ~ '-' ~ problem.probid ~ '"' %} + {% else %} + {% set link = path('public_submissions', {teamId: score.team.teamid, problemId: problem.probid}) %} + {% set extra = 'data-ajax-modal' %} {% endif %} {% if numSubmissions != '0' %} - +
{% if matrixItem.isCorrect %}{{ time }}{% else %} {% endif %} @@ -503,6 +509,16 @@ {% include 'partials/team.html.twig' with {size: 6, team: score.team} %} {% endblock %} {% endembed %} + {% for problem in problems %} + {% embed 'partials/modal.html.twig' with {'modalId': 'team-submissions-modal-' ~ score.team.teamid ~ '-' ~ problem.probid} %} + {% block title %} + Submissions for team {{ score.team.effectiveName }} on problem {{ problem | problemBadge }} {{ problem.problem.name }} + {% endblock %} + {% block content %} + {% include 'public/partials/submission_list.html.twig' with {size: 6, team: score.team, submissions: submissionsPerTeamAndProblem[score.team.teamid][problem.probid] ?? []} %} + {% endblock %} + {% endembed %} + {% endfor %} {% endfor %} {% endif %} diff --git a/webapp/templates/public/partials/submission_list.html.twig b/webapp/templates/public/partials/submission_list.html.twig new file mode 100644 index 0000000000..2277359a34 --- /dev/null +++ b/webapp/templates/public/partials/submission_list.html.twig @@ -0,0 +1,43 @@ +{# Render a list of submissions for the scoreboard #} +{# @var submission \App\Entity\Submission #} + +{% if submissions is empty %} +
No submissions
+{% else %} + + + + + + + + + + {% for submission in submissions %} + + + + + + {% endfor %} + +
timelanguageresult
+ {{ submission.submittime | printtime(null, submission.contest) }} + + {{ submission.language.langid }} + + {% if submission.submittime >= submission.contest.endtime %} + {{ 'too-late' | printResult }} + {% elseif submission.contest.freezetime and submission.submittime >= submission.contest.freezetime and not contest.freezeData.showFinal %} + {{ '' | printResult }} + {% else %} + {% if submission.judgings.first is empty or submission.judgings.first.result is empty %} + {{ '' | printResult }} + {% elseif verificationRequired and not submission.judgings.first.verified %} + {{ '' | printResult }} + {% else %} + {{ submission.judgings.first.result | printResult(true, false, true) }} + {% endif %} + {% endif %} +
+{% endif %} diff --git a/webapp/templates/public/team_submissions.html.twig b/webapp/templates/public/team_submissions.html.twig new file mode 100644 index 0000000000..3728501603 --- /dev/null +++ b/webapp/templates/public/team_submissions.html.twig @@ -0,0 +1,13 @@ +{% extends "public/base.html.twig" %} + +{% block title %} + {% if team is not empty %}Submissions for team {{ team.effectiveName }} on problem {{ problem.problem.name }} - {% endif %}{{ parent() }} +{% endblock %} + +{% block content %} +

+ Submissions for team {{ team.effectiveName }} on problem {{ problem | problemBadge }} {{ problem.problem.name }} +

+ + {% include 'public/partials/submission_list.html.twig' %} +{% endblock %} diff --git a/webapp/templates/public/team_submissions_modal.html.twig b/webapp/templates/public/team_submissions_modal.html.twig new file mode 100644 index 0000000000..cc3ffc4d85 --- /dev/null +++ b/webapp/templates/public/team_submissions_modal.html.twig @@ -0,0 +1,9 @@ +{% extends "partials/modal.html.twig" %} + +{% block title %} + Submissions for team {{ team.effectiveName }} on problem {{ problem | problemBadge }} {{ problem.problem.name }} +{% endblock %} + +{% block content %} + {% include 'public/partials/submission_list.html.twig' %} +{% endblock %} diff --git a/webapp/templates/team/partials/submission_list.html.twig b/webapp/templates/team/partials/submission_list.html.twig index 77b96645b1..588afb2457 100644 --- a/webapp/templates/team/partials/submission_list.html.twig +++ b/webapp/templates/team/partials/submission_list.html.twig @@ -51,11 +51,11 @@
- {%- if submission.submittime > submission.contest.endtime %} - {{ 'too-late' | printResult }} + {%- if submission.submittime >= submission.contest.endtime %} + {{ 'too-late' | printResult }} {%- endif %} - {%- if submission.submittime <= submission.contest.endtime or showTooLateResult %} - {%- if submission.submittime > submission.contest.endtime %} + {%- if submission.submittime < submission.contest.endtime or showTooLateResult %} + {%- if submission.submittime >= submission.contest.endtime %} / {% endif %} {%- if submission.judgings.first is empty or submission.judgings.first.result is empty %}