From d7cc716216ec0261864e02d844cfd0207264616f Mon Sep 17 00:00:00 2001 From: Maarten Sijm <9739541+mpsijm@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:59:19 +0100 Subject: [PATCH 01/23] #2307 Multi-pass: copy feedback dir between passes (but remove nextpass.in) (cherry picked from commit e2de765d200435c90cba78e0e0c166487839266b) --- judge/judgedaemon.main.php | 9 +++++++++ judge/testcase_run.sh | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index 262d3a1004..83178fcc66 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -1410,6 +1410,15 @@ function judge(array $judgeTask): bool $passdir = $testcasedir . '/' . $passCnt; mkdir($passdir, 0755, true); + if ($passCnt > 1) { + $cpcmd = 'cp -R ' . $passdir . '/../' . ($passCnt - 1) . '/feedback ' . $passdir . '/'; + system($cpcmd); + logmsg(LOG_INFO, " Copying feedback dir from pass " . ($passCnt - 1) . ': ' . $cpcmd); + $rmcmd = 'rm ' . $passdir . '/feedback/nextpass.in'; + system($rmcmd); + logmsg(LOG_INFO, " Executing " . $rmcmd); + } + // Copy program with all possible additional files to testcase // dir. Use hardlinks to preserve space with big executables. $programdir = $passdir . '/execdir'; diff --git a/judge/testcase_run.sh b/judge/testcase_run.sh index 7cffaa7ec1..fa316ee76b 100755 --- a/judge/testcase_run.sh +++ b/judge/testcase_run.sh @@ -195,7 +195,7 @@ if [ $COMBINED_RUN_COMPARE -eq 1 ]; then # A combined run and compare script may now already need the # feedback directory, and perhaps access to the test answers (but # only the original that lives outside the chroot). - mkdir feedback + mkdir -p feedback RUNARGS="$RUNARGS $TESTOUT compare.meta feedback" fi @@ -232,8 +232,8 @@ if [ $COMBINED_RUN_COMPARE -eq 0 ]; then exitcode=0 # Create dir for feedback files and make it writable for $RUNUSER - mkdir feedback - chmod a+w feedback + mkdir -p feedback + chmod -R a+w feedback runcheck $GAINROOT "$RUNGUARD" ${DEBUG:+-v} $CPUSET_OPT -u "$RUNUSER" -g "$RUNGROUP" \ -m $SCRIPTMEMLIMIT -t $SCRIPTTIMELIMIT --no-core \ From 4885ad3f9ab8c5fcb4b402c2c175148c36cdadbf Mon Sep 17 00:00:00 2001 From: Tobias Werth Date: Sat, 23 Mar 2024 10:56:24 +0100 Subject: [PATCH 02/23] Ugly code for a mobile-friendly view, only for demonstration purposes. (cherry picked from commit b93d0ad90deb8947ca5ee6d0f1f5d6017bd3f79d) --- webapp/public/style_domjudge.css | 21 +++ webapp/src/Twig/TwigExtension.php | 37 ++++ .../partials/scoreboard_table.html.twig | 173 +++++++++++++++++- 3 files changed, 228 insertions(+), 3 deletions(-) diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index e443f7271c..6962ee6662 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -699,3 +699,24 @@ blockquote { padding: 3px; border-radius: 5px; } + +.strike-diagonal { + position: relative; + text-align: center; +} + +.strike-diagonal:before { + position: absolute; + content: ""; + left: 0; + top: 50%; + right: 0; + border-top: 2px solid; + border-color: firebrick; + + -webkit-transform:rotate(-35deg); + -moz-transform:rotate(-35deg); + -ms-transform:rotate(-35deg); + -o-transform:rotate(-35deg); + transform:rotate(-35deg); +} \ No newline at end of file diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 2a73e324e4..93f7ebdc38 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -23,6 +23,7 @@ use App\Service\DOMJudgeService; use App\Service\EventLogService; use App\Service\SubmissionService; +use App\Utils\Scoreboard\ScoreboardMatrixItem; use App\Utils\Utils; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; @@ -110,6 +111,7 @@ public function getFilters(): array new TwigFilter('fileTypeIcon', $this->fileTypeIcon(...)), new TwigFilter('problemBadge', $this->problemBadge(...), ['is_safe' => ['html']]), new TwigFilter('problemBadgeForContest', $this->problemBadgeForContest(...), ['is_safe' => ['html']]), + new TwigFilter('problemBadgeMaybe', $this->problemBadgeMaybe(...), ['is_safe' => ['html']]), new TwigFilter('printMetadata', $this->printMetadata(...), ['is_safe' => ['html']]), new TwigFilter('printWarningContent', $this->printWarningContent(...), ['is_safe' => ['html']]), new TwigFilter('entityIdBadge', $this->entityIdBadge(...), ['is_safe' => ['html']]), @@ -1091,6 +1093,41 @@ public function problemBadge(ContestProblem $problem, bool $grayedOut = false): ); } + public function problemBadgeMaybe(ContestProblem $problem, ScoreboardMatrixItem $matrixItem): string + { + $rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff'); + if (!$matrixItem->isCorrect) { + $rgb = 'whitesmoke'; + } + $background = Utils::parseHexColor($rgb); + + // Pick a border that's a bit darker. + $darker = $background; + $darker[0] = max($darker[0] - 64, 0); + $darker[1] = max($darker[1] - 64, 0); + $darker[2] = max($darker[2] - 64, 0); + $border = Utils::rgbToHex($darker); + + // Pick the foreground text color based on the background color. + $foreground = ($background[0] + $background[1] + $background[2] > 450) ? '#000000' : '#ffffff'; + if (!$matrixItem->isCorrect) { + $foreground = 'silver'; + $border = 'linen'; + } + + $ret = sprintf( + '%s', + $rgb, + $border, + $foreground, + $problem->getShortname() + ); + if (!$matrixItem->isCorrect && $matrixItem->numSubmissions > 0) { + $ret = '' . $ret . ''; + } + return $ret; + } + public function problemBadgeForContest(Problem $problem, ?Contest $contest = null): string { $contest ??= $this->dj->getCurrentContest(); diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index b0f64f2eab..f4bad952ed 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -28,7 +28,7 @@ {% endif %} - +
{# output table column groups (for the styles) #} @@ -316,6 +316,173 @@
+ + + {# output table column groups (for the styles) #} + + + {% if showFlags %} + + {% else %} + + {% endif %} + {% if showAffiliationLogos %} + + {% endif %} + + + + + + + {% set teamColspan = 2 %} + {% if showAffiliationLogos %} + {% set teamColspan = teamColspan + 1 %} + {% endif %} + + + + + + + + + {% set previousSortOrder = -1 %} + {% set previousTeam = null %} + {% set backgroundColors = {"#FFFFFF": 1} %} + {% set medalCount = 0 %} + {% for score in scores %} + {% set classes = [] %} + {% if score.team.category.sortorder != previousSortOrder %} + {% if previousSortOrder != -1 %} + {# Output summary of previous sort order #} + {% include 'partials/scoreboard_summary.html.twig' with {sortOrder: previousSortOrder} %} + {% endif %} + {% set classes = classes | merge(['sortorderswitch']) %} + {% set previousSortOrder = score.team.category.sortorder %} + {% set previousTeam = null %} + {% endif %} + + {# process medal color #} + {% set medalColor = '' %} + {% if showLegends %} + {% set medalColor = score.team | medalType(contest, scoreboard) %} + {% endif %} + + {# check whether this is us, otherwise use category colour #} + {% if myTeamId is defined and myTeamId == score.team.teamid %} + {% set classes = classes | merge(['scorethisisme']) %} + {% set color = '#FFFF99' %} + {% else %} + {% set color = score.team.category.color %} + {% endif %} + + + + {% if showAffiliationLogos %} + + {% endif %} + {% if color is null %} + {% set color = "#FFFFFF" %} + {% set colorClass = "_FFFFFF" %} + {% else %} + {% set colorClass = color | replace({"#": "_"}) %} + {% set backgroundColors = backgroundColors | merge({(color): 1}) %} + {% endif %} + + {% set totalTime = score.totalTime %} + {% if scoreInSeconds %} + {% set totalTime = totalTime | printTimeRelative %} + {% endif %} + {% set totalPoints = score.numPoints %} + + + + + + {% endfor %} + +
rankteamscore
+ {# Only print rank when score is different from the previous team #} + {% if not displayRank %} + ? + {% elseif previousTeam is null or scoreboard.scores[previousTeam.teamid].rank != score.rank %} + {{ score.rank }} + {% else %} + {% endif %} + {% set previousTeam = score.team %} + + {% if showFlags %} + {% if score.team.affiliation %} + {% set link = null %} + {% if jury %} + {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.affilid}) %} + {% endif %} + + {{ score.team.affiliation.country|countryFlag }} + + {% endif %} + {% endif %} + + {% if score.team.affiliation %} + {% set link = null %} + {% if jury %} + {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.affilid}) %} + {% endif %} + + {% set affiliationId = score.team.affiliation.externalid %} + {% set affiliationImage = affiliationId | assetPath('affiliation') %} + {% if affiliationImage %} + {{ score.team.affiliation.name }} + {% else %} + {{ affiliationId }} + {% endif %} + + {% endif %} + + {% set link = null %} + {% set extra = null %} + {% if static %} + {% set link = '#' %} + {% set extra = 'data-bs-toggle="modal" data-bs-target="#team-modal-' ~ score.team.teamid ~ '"' %} + {% else %} + {% if jury %} + {% set link = path('jury_team', {teamId: score.team.teamid}) %} + {% elseif public %} + {% set link = path('public_team', {teamId: score.team.teamid}) %} + {% set extra = 'data-ajax-modal' %} + {% else %} + {% set link = path('team_team', {teamId: score.team.teamid}) %} + {% set extra = 'data-ajax-modal' %} + {% endif %} + {% endif %} + + + {% if false and usedCategories | length > 1 and scoreboard.bestInCategory(score.team, limitToTeamIds) %} + + {{ score.team.category.name }} + + {% endif %} + {{ score.team.effectiveName }} + + {% if showAffiliations %} + + {% if score.team.affiliation %} + {{ score.team.affiliation.name }} + {% endif %} + + {% endif %} + + {{ totalPoints }}
{{ totalTime }}
+ + {% for problem in problems %} + {% set matrixItem = scoreboard.matrix[score.team.teamid][problem.probid] %} + {{ problem | problemBadgeMaybe(matrixItem) }} + {% endfor %} +
+ {% if static %} {% for score in scores %} {% embed 'partials/modal.html.twig' with {'modalId': 'team-modal-' ~ score.team.teamid} %} @@ -366,7 +533,7 @@ {% else %} {% set cellColors = {first: 'Solved first', correct: 'Solved', incorrect: 'Tried, incorrect', pending: 'Tried, pending', neutral: 'Untried'} %} {% endif %} - +
@@ -385,7 +552,7 @@ {% endif %} {% if medalsEnabled %} -
Cell colours
+
From 591737de54eef50c20aa21d1c623d104425cd9a8 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 5 Apr 2024 15:19:35 +0200 Subject: [PATCH 03/23] Update mobile scoreboard. - Make team names not fall of the screen by calculating their max width. - Make non mobile scoreboard not left-aligned. - Add a bit of margin to the header. - Make the problem boxes right aligned. - Change the card at the top to show 2 lines (name + contestt time) on mobile. (cherry picked from commit 4f6b3ecf7711e43f489f6827e38ecfac5cf6ac59) --- webapp/public/style_domjudge.css | 3 +- .../templates/partials/scoreboard.html.twig | 52 +++++++++++-------- webapp/templates/public/scoreboard.html.twig | 3 +- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index 6962ee6662..7ca835411f 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -219,6 +219,7 @@ del { display: block; overflow: hidden; } + .toolong:after { content: ""; width: 30%; @@ -719,4 +720,4 @@ blockquote { -ms-transform:rotate(-35deg); -o-transform:rotate(-35deg); transform:rotate(-35deg); -} \ No newline at end of file +} diff --git a/webapp/templates/partials/scoreboard.html.twig b/webapp/templates/partials/scoreboard.html.twig index 3c4f58e4e0..4fb8a18bfb 100644 --- a/webapp/templates/partials/scoreboard.html.twig +++ b/webapp/templates/partials/scoreboard.html.twig @@ -18,31 +18,37 @@ {% endif %}
-
- {{ current_contest.name }} - - {% if scoreboard is null %} - {{ current_contest | printContestStart }} - {% elseif scoreboard.freezeData.showFinal(jury) %} - {% if current_contest.finalizetime is empty %} - preliminary results - not final - {% else %} - final standings - {% endif %} - {% elseif scoreboard.freezeData.stopped %} - contest over, waiting for results - {% elseif static %} - {% set now = 'now'|date('U') %} - {{ current_contest.starttime | printelapsedminutes(now) }} - {% else %} - {% if current_contest.freezeData.started %} - started: +
+
+
+ {{ current_contest.name }} +
+
+ + {% if scoreboard is null %} + {{ current_contest | printContestStart }} + {% elseif scoreboard.freezeData.showFinal(jury) %} + {% if current_contest.finalizetime is empty %} + preliminary results - not final + {% else %} + final standings + {% endif %} + {% elseif scoreboard.freezeData.stopped %} + contest over, waiting for results + {% elseif static %} + {% set now = 'now'|date('U') %} + {{ current_contest.starttime | printelapsedminutes(now) }} {% else %} - starts: + {% if current_contest.freezeData.started %} + started: + {% else %} + starts: + {% endif %} + {{ current_contest.starttime | printtime }} - ends: {{ current_contest.endtime | printtime }} {% endif %} - {{ current_contest.starttime | printtime }} - ends: {{ current_contest.endtime | printtime }} - {% endif %} - + +
+
{% if static %} diff --git a/webapp/templates/public/scoreboard.html.twig b/webapp/templates/public/scoreboard.html.twig index 6ab323c152..90b7cddd7a 100644 --- a/webapp/templates/public/scoreboard.html.twig +++ b/webapp/templates/public/scoreboard.html.twig @@ -12,7 +12,7 @@ {% set bannerImage = globalBannerAssetPath() %} {% endif %} {% if bannerImage %} - + {% endif %}
@@ -53,6 +53,7 @@ {% if static and refresh is defined %} disableRefreshOnModal(); {% endif %} + resizeMobileTeamNames(); }; {% if static and refresh is defined %} From 276698f2c3af0bde2fd12a8591d0bc11c795b843 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 5 Apr 2024 16:11:43 +0200 Subject: [PATCH 04/23] Scale problem badges for very small screens. (cherry picked from commit 3cd918fbdbf34c0b7f538c6b86f0b0a1b0dea882) --- webapp/public/style_domjudge.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index 7ca835411f..df57f21594 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -220,6 +220,12 @@ del { overflow: hidden; } +.mobile-problem-badges { + position: relative; + display: block; + white-space: nowrap; +} + .toolong:after { content: ""; width: 30%; From f2d4a3a90c1cebf4db7be5b2000330e268b0413a Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Sat, 6 Apr 2024 13:28:22 +0200 Subject: [PATCH 05/23] Move scoreboard javascript to JS file so browser can cache it. (cherry picked from commit 0a820070d7d6c188e06180487661555cd1c99fb8) --- webapp/public/js/domjudge.js | 49 ++++++++++++++++++++ webapp/templates/public/scoreboard.html.twig | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/webapp/public/js/domjudge.js b/webapp/public/js/domjudge.js index 97dd01eb73..4a2ef98e2d 100644 --- a/webapp/public/js/domjudge.js +++ b/webapp/public/js/domjudge.js @@ -944,3 +944,52 @@ function initializeKeyboardShortcuts() { } }); } + +// Make sure the items in the desktop scoreboard fit +document.querySelectorAll(".desktop-scoreboard .forceWidth:not(.toolong)").forEach(el => { + if (el instanceof Element && el.scrollWidth > el.offsetWidth) { + el.classList.add("toolong"); + } +}); + +/** + * Helper method to resize mobile team names and problem badges + */ +function resizeMobileTeamNamesAndProblemBadges() { + // Make team names fit on the screen, but only when the mobile + // scoreboard is visible + const mobileScoreboard = document.querySelector('.mobile-scoreboard'); + if (mobileScoreboard.offsetWidth === 0) { + return; + } + const windowWidth = document.body.offsetWidth; + const teamNameMaxWidth = Math.max(10, windowWidth - 150); + const problemBadgesMaxWidth = Math.max(10, windowWidth - 78); + document.querySelectorAll(".mobile-scoreboard .forceWidth:not(.toolong)").forEach(el => { + el.classList.remove("toolong"); + el.style.maxWidth = teamNameMaxWidth + 'px'; + if (el instanceof Element && el.scrollWidth > el.offsetWidth) { + el.classList.add("toolong"); + } else { + el.classList.remove("toolong"); + } + }); + document.querySelectorAll(".mobile-scoreboard .mobile-problem-badges:not(.toolong)").forEach(el => { + el.classList.remove("toolong"); + el.style.maxWidth = problemBadgesMaxWidth + 'px'; + if (el instanceof Element && el.scrollWidth > el.offsetWidth) { + el.classList.add("toolong"); + const scale = el.offsetWidth / el.scrollWidth; + const offset = -1 * (el.scrollWidth - el.offsetWidth) / 2; + el.style.transform = `scale(${scale}) translateX(${offset}px)`; + } else { + el.classList.remove("toolong"); + el.style.transform = null; + } + }); +} + +if (document.querySelector('.mobile-scoreboard')) { + window.addEventListener('resize', resizeMobileTeamNamesAndProblemBadges); + resizeMobileTeamNamesAndProblemBadges(); +} diff --git a/webapp/templates/public/scoreboard.html.twig b/webapp/templates/public/scoreboard.html.twig index 90b7cddd7a..e1f0dbb559 100644 --- a/webapp/templates/public/scoreboard.html.twig +++ b/webapp/templates/public/scoreboard.html.twig @@ -53,7 +53,7 @@ {% if static and refresh is defined %} disableRefreshOnModal(); {% endif %} - resizeMobileTeamNames(); + resizeMobileTeamNamesAndProblemBadges(); }; {% if static and refresh is defined %} From 3d94e05c6bc309dba9ca4c22e50e5b97bd320e7e Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Mon, 8 Apr 2024 20:47:36 +0200 Subject: [PATCH 06/23] Run mobile scoreboard logic only after the page has loaded. (cherry picked from commit cbef321ad6ff7bd2d3ad258a914277b1c06be53e) --- webapp/public/js/domjudge.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webapp/public/js/domjudge.js b/webapp/public/js/domjudge.js index 4a2ef98e2d..b41497676b 100644 --- a/webapp/public/js/domjudge.js +++ b/webapp/public/js/domjudge.js @@ -989,7 +989,9 @@ function resizeMobileTeamNamesAndProblemBadges() { }); } -if (document.querySelector('.mobile-scoreboard')) { - window.addEventListener('resize', resizeMobileTeamNamesAndProblemBadges); - resizeMobileTeamNamesAndProblemBadges(); -} +$(function() { + if (document.querySelector('.mobile-scoreboard')) { + window.addEventListener('resize', resizeMobileTeamNamesAndProblemBadges); + resizeMobileTeamNamesAndProblemBadges(); + } +}); From fcbf151d6eaecb389f09a11497617bc3445a639f Mon Sep 17 00:00:00 2001 From: Tobias Werth Date: Sat, 25 May 2024 13:24:01 +0200 Subject: [PATCH 07/23] Fix pending subs on mobile scoreboard. (cherry picked from commit 6f0d7dfa9c693e38f3eada51dbe47d6e6bfff8e6) --- webapp/src/Twig/TwigExtension.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 93f7ebdc38..ae06221a6c 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -1122,8 +1122,12 @@ public function problemBadgeMaybe(ContestProblem $problem, ScoreboardMatrixItem $foreground, $problem->getShortname() ); - if (!$matrixItem->isCorrect && $matrixItem->numSubmissions > 0) { - $ret = '' . $ret . ''; + if (!$matrixItem->isCorrect) { + if ($matrixItem->numSubmissionsPending > 0) { + $ret = '' . $ret . ''; + } else if ($matrixItem->numSubmissions > 0) { + $ret = '' . $ret . ''; + } } return $ret; } From eae5f41d77897bd5ffe8c03ab52e288063ea9bee Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 6 Sep 2024 11:49:30 +0200 Subject: [PATCH 08/23] Display as table to center align properly (cherry picked from commit ef836fe60e35579c36249ae6ff9cc46e0b8fda82) --- webapp/templates/partials/scoreboard_table.html.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index f4bad952ed..761fd2c6ca 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -28,7 +28,7 @@ {% endif %} -
Medals {% if not scoreboard.freezeData.showFinal %}(tentative){% endif %}
+
{# output table column groups (for the styles) #} @@ -533,7 +533,7 @@ {% else %} {% set cellColors = {first: 'Solved first', correct: 'Solved', incorrect: 'Tried, incorrect', pending: 'Tried, pending', neutral: 'Untried'} %} {% endif %} -
+
@@ -552,7 +552,7 @@ {% endif %} {% if medalsEnabled %} -
Cell colours
+
From 33edd074295a42b8f7d864e9be78e4bcf0896053 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Wed, 23 Oct 2024 20:03:57 +0200 Subject: [PATCH 09/23] Hide scoreboard summary between sortorders for mobile scoreboard (cherry picked from commit c8a5ef7f0ac716f3278205852afbcd00288c5ca6) --- webapp/templates/partials/scoreboard_table.html.twig | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index 761fd2c6ca..9fbba7c52b 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -354,10 +354,6 @@ {% for score in scores %} {% set classes = [] %} {% if score.team.category.sortorder != previousSortOrder %} - {% if previousSortOrder != -1 %} - {# Output summary of previous sort order #} - {% include 'partials/scoreboard_summary.html.twig' with {sortOrder: previousSortOrder} %} - {% endif %} {% set classes = classes | merge(['sortorderswitch']) %} {% set previousSortOrder = score.team.category.sortorder %} {% set previousTeam = null %} @@ -471,7 +467,7 @@ - {# output table column groups (for the styles) #} - + {% if enable_ranking %} + + {% endif %} {% if showFlags %} {% else %} @@ -331,9 +333,11 @@ {% endif %} - - - + {% if enable_ranking %} + + + + {% endif %} {% set teamColspan = 2 %} {% if showAffiliationLogos %} @@ -341,9 +345,13 @@ {% endif %} - + {% if enable_ranking %} + + {% endif %} - + {% if enable_ranking %} + + {% endif %} @@ -373,16 +381,18 @@ {% set color = score.team.category.color %} {% endif %} - + {% if enable_ranking %} + + {% endif %} - {% set totalTime = score.totalTime %} - {% if scoreInSeconds %} - {% set totalTime = totalTime | printTimeRelative %} + {% if enable_ranking %} + {% set totalTime = score.totalTime %} + {% if scoreInSeconds %} + {% set totalTime = totalTime | printTimeRelative %} + {% endif %} + {% set totalPoints = score.numPoints %} + {% endif %} - {% set totalPoints = score.numPoints %} - -
Medals {% if not scoreboard.freezeData.showFinal %}(tentative){% endif %}{{ totalPoints }}
{{ totalTime }}
+ {% for problem in problems %} {% set matrixItem = scoreboard.matrix[score.team.teamid][problem.probid] %} From 40c91dd026ccc4c8f4924a60032765e2069d36bf Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Wed, 23 Oct 2024 20:04:08 +0200 Subject: [PATCH 10/23] Hide ugly bar before problem badges on mobile scoreboard (cherry picked from commit 5e51d8111040c65951d9f460568dd5936d6e3480) --- webapp/public/style_domjudge.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index df57f21594..cccfe2d517 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -195,6 +195,9 @@ del { border-right: 1px solid silver; padding: 0; } +.scoreboard td.no-border, .scoreboard th.no-border { + border: none; +} .scoreboard td.score_cell { min-width: 4.2em; border-right: none; From e3a730ef10a826458c22e4c3c9b814aba622aa6a Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 12:13:51 +0100 Subject: [PATCH 11/23] Disable ranking on mobile scoreboard when requested (cherry picked from commit 38447d5222ac43e7dbffc9e222ad4beaac8a51c2) --- .../partials/scoreboard_table.html.twig | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index 9fbba7c52b..ca6203fb82 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -320,7 +320,9 @@
rankrankteamscorescore
- {# Only print rank when score is different from the previous team #} - {% if not displayRank %} - ? - {% elseif previousTeam is null or scoreboard.scores[previousTeam.teamid].rank != score.rank %} - {{ score.rank }} - {% else %} - {% endif %} - {% set previousTeam = score.team %} - + {# Only print rank when score is different from the previous team #} + {% if not displayRank %} + ? + {% elseif previousTeam is null or scoreboard.scores[previousTeam.teamid].rank != score.rank %} + {{ score.rank }} + {% else %} + {% endif %} + {% set previousTeam = score.team %} + {% if showFlags %} {% if score.team.affiliation %} @@ -459,12 +469,14 @@ {% endif %} {{ totalPoints }}
{{ totalTime }}
{{ totalPoints }}
{{ totalTime }}
From e6ba5063ddec6a19f0a744d03dd91bf9ba6e781f Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 12:26:23 +0100 Subject: [PATCH 12/23] Allow problem badges to go below country flag and affiliation logo (cherry picked from commit 95deac3cbb1b0cb6941ffce15edc6b6c55e895d6) --- webapp/templates/partials/scoreboard_table.html.twig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index ca6203fb82..c7292c1dc0 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -479,8 +479,12 @@ {% endif %}
- + {% if showAffiliationLogos %} + {% set problemSpan = 3 %} + {% else %} + {% set problemSpan = 2 %} + {% endif %} + {% for problem in problems %} {% set matrixItem = scoreboard.matrix[score.team.teamid][problem.probid] %} {{ problem | problemBadgeMaybe(matrixItem) }} From 7acfad4f8df4c924bc20dbfaaebce5443b8cd125 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 12:36:20 +0100 Subject: [PATCH 13/23] Move colgroups to correct position (cherry picked from commit 3adc23824c55633b6fa3acb173b543121ddf0844) --- .../templates/partials/scoreboard_table.html.twig | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index c7292c1dc0..b186cde787 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -30,6 +30,11 @@ + {% set teamColspan = 2 %} + {% if showAffiliationLogos %} + {% set teamColspan = teamColspan + 1 %} + {% endif %} + {# output table column groups (for the styles) #} {% if enable_ranking %} @@ -58,12 +63,6 @@ {% endfor %} {% endif %} - - {% set teamColspan = 2 %} - {% if showAffiliationLogos %} - {% set teamColspan = teamColspan + 1 %} - {% endif %} - {% if enable_ranking %} @@ -317,7 +316,6 @@
- {# output table column groups (for the styles) #} {% if enable_ranking %} @@ -338,6 +336,7 @@ {% endif %} + {% set teamColspan = 2 %} {% if showAffiliationLogos %} From 611ff9cd0fb8a728e3d819c60cb51bb0ace8fa9a Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 13:37:33 +0100 Subject: [PATCH 14/23] Fix HTML W3C stuff (cherry picked from commit 108970b57ff528f806cd4330d2e311dee9bc04eb) --- webapp/public/style_domjudge.css | 2 +- webapp/templates/partials/scoreboard_table.html.twig | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/webapp/public/style_domjudge.css b/webapp/public/style_domjudge.css index cccfe2d517..7b8db354a2 100644 --- a/webapp/public/style_domjudge.css +++ b/webapp/public/style_domjudge.css @@ -284,7 +284,7 @@ img.affiliation-logo { .silver-medal { background-color: #aaa } .bronze-medal { background-color: #c08e55 } -#scoresolv,#scoretotal { width: 2.5em; } +#scoresolv,#scoretotal,#scoresolvmobile,#scoretotalmobile { width: 2.5em; } .scorenc,.scorett,.scorepl { text-align: center; width: 2ex; } .scorenc { font-weight: bold; } td.scorenc { border-color: silver; border-right: 0; } diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index b186cde787..5077ca53f3 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -38,22 +38,22 @@ {# output table column groups (for the styles) #} {% if enable_ranking %} - + {% endif %} {% if showFlags %} - + {% else %} {% endif %} {% if showAffiliationLogos %} - + {% endif %} - + {% if enable_ranking %} - - + + {% endif %} From 6d5635d441148da9b1d0aa882874d79ac447de05 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 13:45:38 +0100 Subject: [PATCH 15/23] Fix double team:xxx ID's (cherry picked from commit 706fc05ff61fd8c778d6a7ba31d4738c0f538636) --- webapp/public/js/domjudge.js | 4 +--- webapp/templates/partials/scoreboard_table.html.twig | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/webapp/public/js/domjudge.js b/webapp/public/js/domjudge.js index b41497676b..c0fe0496aa 100644 --- a/webapp/public/js/domjudge.js +++ b/webapp/public/js/domjudge.js @@ -246,9 +246,7 @@ function getHeartCol(row) { function getTeamname(row) { - var res = row.getAttribute("id"); - if ( res === null ) return res; - return res.replace(/^team:/, ''); + return row.getAttribute("data-team-id"); } function toggle(id, show) diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index 5077ca53f3..3d0d9f9607 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -133,7 +133,7 @@ {% else %} {% set color = score.team.category.color %} {% endif %} - + {% if enable_ranking %} + {% if enable_ranking %}
{# Only print rank when score is different from the previous team #} @@ -379,7 +379,7 @@ {% else %} {% set color = score.team.category.color %} {% endif %} -
{# Only print rank when score is different from the previous team #} From d2144c281520167b3b77e8535eeb4cc6477c8f65 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 14:14:34 +0100 Subject: [PATCH 16/23] Fix elseif (cherry picked from commit 6a6ae1067f6880388bf88e926c53a9d167c25a68) --- webapp/src/Twig/TwigExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index ae06221a6c..a496f540be 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -1125,7 +1125,7 @@ public function problemBadgeMaybe(ContestProblem $problem, ScoreboardMatrixItem if (!$matrixItem->isCorrect) { if ($matrixItem->numSubmissionsPending > 0) { $ret = '' . $ret . ''; - } else if ($matrixItem->numSubmissions > 0) { + } elseif ($matrixItem->numSubmissions > 0) { $ret = '' . $ret . ''; } } From 7ee7fa0a6074f5f8eba82c548977bc5f99bae935 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 15:31:39 +0100 Subject: [PATCH 17/23] Fix hearts for scoreboard on local filesystem Fixes #2724 (cherry picked from commit 42bb954aa4be6b13b3c337b82a99e8eeeaf00986) --- webapp/public/js/domjudge.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/webapp/public/js/domjudge.js b/webapp/public/js/domjudge.js index c0fe0496aa..2135c38bb8 100644 --- a/webapp/public/js/domjudge.js +++ b/webapp/public/js/domjudge.js @@ -200,7 +200,7 @@ function getCookie(name) function getSelectedTeams() { - var cookieVal = getCookie("domjudge_teamselection"); + var cookieVal = localStorage.getItem("domjudge_teamselection"); if (cookieVal === null || cookieVal === "") { return new Array(); } @@ -284,10 +284,15 @@ function toggle(id, show) } var cookieVal = JSON.stringify(favTeams); - setCookie("domjudge_teamselection", cookieVal); + localStorage.setItem("domjudge_teamselection", cookieVal); $('.loading-indicator').addClass('ajax-loader'); + // If we are on a local file system, reload the window + if (window.location.protocol === 'file:') { + window.location.reload(); + return; + } $.ajax({ url: scoreboardUrl, cache: false From 121276b9bb23f5e75aa662362b98a6cdadd5b4ee Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 15:18:27 +0100 Subject: [PATCH 18/23] =?UTF-8?q?Allow=20to=20=E2=9D=A4=EF=B8=8F=20on=20mo?= =?UTF-8?q?bile=20scoreboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #2762. (cherry picked from commit a53c4816cce4fcab4bcca4075557de4de797ae77) --- webapp/public/js/domjudge.js | 164 ++++++++++++------ .../partials/scoreboard_table.html.twig | 30 ++-- 2 files changed, 125 insertions(+), 69 deletions(-) diff --git a/webapp/public/js/domjudge.js b/webapp/public/js/domjudge.js index 2135c38bb8..2e7e2097de 100644 --- a/webapp/public/js/domjudge.js +++ b/webapp/public/js/domjudge.js @@ -207,13 +207,29 @@ function getSelectedTeams() return JSON.parse(cookieVal); } -function getScoreboard() +function getScoreboards(mobile) { - var scoreboard = document.getElementsByClassName("scoreboard"); - if (scoreboard === null || scoreboard[0] === null || scoreboard[0] === undefined) { + const scoreboards = document.getElementsByClassName("scoreboard"); + if (scoreboards === null || scoreboards[0] === null || scoreboards[0] === undefined) { return null; } - return scoreboard[0].rows; + let scoreboardRows = {}; + const mobileScoreboardClass = 'mobile-scoreboard'; + const desktopScoreboardClass = 'desktop-scoreboard'; + for (let i = 0; i < scoreboards.length; i++) { + if (scoreboards[i].classList.contains(mobileScoreboardClass)) { + scoreboardRows.mobile = scoreboards[i].rows; + } else if (scoreboards[i].classList.contains(desktopScoreboardClass)) { + scoreboardRows.desktop = scoreboards[i].rows; + } + } + if (mobile === undefined) { + return scoreboardRows; + } else if (mobile) { + return scoreboardRows.mobile; + } else { + return scoreboardRows.desktop; + } } function getRank(row) @@ -226,7 +242,7 @@ function getHeartCol(row) { var td = null; // search for td before the team name for (var i = 1; i < 4; i++) { - if (tds[i].className == "scoretn") { + if (tds[i].classList.contains("scoretn")) { td = tds[i - 1]; break; } @@ -249,16 +265,28 @@ function getTeamname(row) return row.getAttribute("data-team-id"); } -function toggle(id, show) +function toggle(id, show, mobile) { - var scoreboard = getScoreboard(); + var scoreboard = getScoreboards(mobile); if (scoreboard === null) return; + // Filter out all rows that do not have a data-team-id attribute or have + // the class `scoreheader`. + // The mobile scoreboard has them, and we need to ignore them. + scoreboard = Array.from(scoreboard) + .filter( + row => row.getAttribute("data-team-id") + || row.classList.contains("scoreheader") + ); + var favTeams = getSelectedTeams(); // count visible favourite teams (if filtered) var visCnt = 0; for (var i = 0; i < favTeams.length; i++) { for (var j = 0; j < scoreboard.length; j++) { + if (!scoreboard[j].getAttribute("data-team-id")) { + continue; + } var scoreTeamname = getTeamname(scoreboard[j]); if (scoreTeamname === null) { continue; @@ -302,69 +330,99 @@ function toggle(id, show) }); } -function addHeart(rank, row, id, isFav) +function addHeart(rank, row, id, isFav, mobile) { var heartCol = getHeartCol(row); var iconClass = isFav ? "fas fa-heart" : "far fa-heart"; - return heartCol.innerHTML + ""; + return heartCol.innerHTML + ""; } function initFavouriteTeams() { - var scoreboard = getScoreboard(); - if (scoreboard === null) { + const scoreboards = getScoreboards(); + if (scoreboards === null) { return; } var favTeams = getSelectedTeams(); - var toAdd = new Array(); - var cntFound = 0; - var lastRank = 0; - for (var j = 0; j < scoreboard.length; j++) { - var found = false; - var teamname = getTeamname(scoreboard[j]); - if (teamname === null) { - continue; - } - var firstCol = getRank(scoreboard[j]); - var heartCol = getHeartCol(scoreboard[j]); - var rank = firstCol.innerHTML; - for (var i = 0; i < favTeams.length; i++) { - if (teamname === favTeams[i]) { - found = true; - heartCol.innerHTML = addHeart(rank, scoreboard[j], j, found); - toAdd[cntFound] = scoreboard[j].cloneNode(true); - if (rank.trim().length === 0) { - // make rank explicit in case of tie - getRank(toAdd[cntFound]).innerHTML += lastRank; + Object.keys(scoreboards).forEach(function(key) { + var toAdd = new Array(); + var toAddMobile = new Array(); + var cntFound = 0; + var lastRank = 0; + const scoreboard = scoreboards[key]; + const mobile = key === 'mobile'; + let teamIndex = 1; + for (var j = 0; j < scoreboard.length; j++) { + var found = false; + var teamname = getTeamname(scoreboard[j]); + if (teamname === null) { + continue; + } + var firstCol = getRank(scoreboard[j]); + var heartCol = getHeartCol(scoreboard[j]); + var rank = firstCol.innerHTML; + for (var i = 0; i < favTeams.length; i++) { + if (teamname === favTeams[i]) { + found = true; + heartCol.innerHTML = addHeart(rank, scoreboard[j], teamIndex, found, mobile); + toAdd[cntFound] = scoreboard[j].cloneNode(true); + if (mobile) { + toAddMobile[cntFound] = scoreboard[j + 1].cloneNode(true); + } + if (rank.trim().length === 0) { + // make rank explicit in case of tie + getRank(toAdd[cntFound]).innerHTML += lastRank; + } + scoreboard[j].style.background = "lightyellow"; + const scoretn = scoreboard[j].querySelector('.scoretn'); + if (scoretn && scoretn.classList.contains('cl_FFFFFF')) { + scoretn.classList.remove('cl_FFFFFF'); + scoretn.classList.add('cl_FFFFE0'); + } + if (mobile) { + scoreboard[j + 1].style.background = "lightyellow"; + } + cntFound++; + break; } - scoreboard[j].style.background = "lightyellow"; - cntFound++; - break; } + if (!found) { + heartCol.innerHTML = addHeart(rank, scoreboard[j], teamIndex, found, mobile); + } + if (rank !== "") { + lastRank = rank; + } + + teamIndex++; } - if (!found) { - heartCol.innerHTML = addHeart(rank, scoreboard[j], j, found); - } - if (rank !== "") { - lastRank = rank; - } - } - // copy favourite teams to the top of the scoreboard - for (var i = 0; i < cntFound; i++) { - var copy = toAdd[i]; - var style = ""; - if (i === 0) { - style += "border-top: 2px solid black;"; + let addCounter = 1; + const copyRow = function (i, copy, addTopBorder, addBottomBorder, noMiddleBorder) { + let style = ""; + if (noMiddleBorder) { + style += "border-bottom-width: 0;"; + } + if (addTopBorder && i === 0) { + style += "border-top: 2px solid black;"; + } + if (addBottomBorder && i === cntFound - 1) { + style += "border-bottom: thick solid black;"; + } + copy.setAttribute("style", style); + const tbody = scoreboard[1].parentNode; + tbody.insertBefore(copy, scoreboard[addCounter]); + addCounter++; } - if (i === cntFound - 1) { - style += "border-bottom: thick solid black;"; + + // copy favourite teams to the top of the scoreboard + for (let i = 0; i < cntFound; i++) { + copyRow(i, toAdd[i], true, !mobile, mobile); + if (mobile) { + copyRow(i, toAddMobile[i], false, true, false); + } } - copy.setAttribute("style", style); - var tbody = scoreboard[1].parentNode; - tbody.insertBefore(copy, scoreboard[i + 1]); - } + }); } // This function is a specific addition for using DOMjudge within a diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index 3d0d9f9607..1d2591fc82 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -28,7 +28,7 @@ {% endif %} - +
{% set teamColspan = 2 %} {% if showAffiliationLogos %} @@ -38,22 +38,22 @@ {# output table column groups (for the styles) #} {% if enable_ranking %} - + {% endif %} {% if showFlags %} - + {% else %} {% endif %} {% if showAffiliationLogos %} - + {% endif %} - + {% if enable_ranking %} - - + + {% endif %} @@ -106,7 +106,7 @@ {% set previousSortOrder = -1 %} {% set previousTeam = null %} - {% set backgroundColors = {"#FFFFFF": 1} %} + {% set backgroundColors = {"#FFFFFF": 1, '#FFFFE0': 1} %} {% set medalCount = 0 %} {% for score in scores %} {% set classes = [] %} @@ -315,25 +315,25 @@
- +
{# output table column groups (for the styles) #} {% if enable_ranking %} - + {% endif %} {% if showFlags %} - + {% else %} {% endif %} {% if showAffiliationLogos %} - + {% endif %} - + {% if enable_ranking %} - + {% endif %} @@ -356,7 +356,6 @@ {% set previousSortOrder = -1 %} {% set previousTeam = null %} - {% set backgroundColors = {"#FFFFFF": 1} %} {% set medalCount = 0 %} {% for score in scores %} {% set classes = [] %} @@ -431,7 +430,6 @@ {% set colorClass = "_FFFFFF" %} {% else %} {% set colorClass = color | replace({"#": "_"}) %} - {% set backgroundColors = backgroundColors | merge({(color): 1}) %} {% endif %}
{% set link = null %} From 9b03ea119b9cf2317ec223c5e24ed9c171387c1d Mon Sep 17 00:00:00 2001 From: Tobias Werth Date: Sat, 23 Nov 2024 11:45:00 +0100 Subject: [PATCH 19/23] Fix handling of domserver errors in judgedaemon. - Treat 500 / internal server error as retry-able - If we still fail after all configured retries, we do mark the endpoint as errored and sleep for a while longer. Afterwards, we are registering ourselves again and if that succeeds will try to fetch work again. (cherry picked from commit c850eb36272e90fcea7727014dd8c1a83be71ce0) --- judge/judgedaemon.main.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index 83178fcc66..3355db24ac 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -164,9 +164,6 @@ function request(string $url, string $verb = 'GET', $data = '', bool $failonerro ": http status code: " . $status . ", request size = " . strlen(print_r($data, true)) . ", response: " . $response; - if ($status == 500) { - break; - } } else { $succeeded = true; break; @@ -751,9 +748,12 @@ function fetch_executable_internal( // Request open submissions to judge. Any errors will be treated as // non-fatal: we will just keep on retrying in this loop. + $row = []; $judging = request('judgehosts/fetch-work', 'POST', ['hostname' => $myhost], false); - // If $judging is null, an error occurred; don't try to decode. - if (!is_null($judging)) { + // If $judging is null, an error occurred; we marked the endpoint already as errorred above. + if (is_null($judging)) { + continue; + } else { $row = dj_json_decode($judging); } From 116e9cfa1799b763dff5f3f7c9e19dffa933f6a0 Mon Sep 17 00:00:00 2001 From: Tobias Werth Date: Fri, 22 Nov 2024 17:30:30 +0100 Subject: [PATCH 20/23] Indicate to the judgehost to retry if we are cleaning up old queue tasks. Related: https://github.com/DOMjudge/domjudge/commit/80c1a43bfb85013ae5fa02b4bf60c4b3527d08f0 (cherry picked from commit 780c7c91cba50005f72ba74efe8d9cec0357322a) --- judge/judgedaemon.main.php | 12 ++++++++++++ webapp/src/Controller/API/JudgehostController.php | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index 3355db24ac..5d58d45dfe 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -60,6 +60,7 @@ function read_credentials(): void "waiting" => false, "errorred" => false, "last_attempt" => -1, + "retrying" => false, ]; } if (count($endpoints) <= 0) { @@ -780,8 +781,19 @@ function fetch_executable_internal( // We have gotten a work packet. $endpoints[$endpointID]["waiting"] = false; + // All tasks are guaranteed to be of the same type. $type = $row[0]['type']; + + if ($type == 'try_again') { + if (!$endpoints[$endpointID]['retrying']) { + logmsg(LOG_INFO, "API indicated to retry fetching work (this might take a while to clean up)."); + } + $endpoints[$endpointID]['retrying'] = true; + continue; + } + $endpoints[$endpointID]['retrying'] = false; + logmsg(LOG_INFO, "⇝ Received " . sizeof($row) . " '" . $type . "' judge tasks (endpoint $endpointID)"); diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 8eb6239515..a644a944c9 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1594,8 +1594,16 @@ public function getJudgeTasksAction(Request $request): array ->setMaxResults(1) ->getQuery() ->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR); + if ($jobid === null) { + return; + } $judgetasks = $this->getJudgetasks($jobid, $max_batchsize, $judgehost); - if ($judgetasks !== null) { + if (empty($judgetasks)) { + // Somehow we got ourselves in a situation that there was a queue task without remaining judge tasks. + // This should not happen, but if it does, we need to clean up. Each of the fetch-work calls will clean + // up one queue task. We need to signal to the judgehost that there might be more work to do. + $judgetasks = [['type' => 'try_again']]; + } else { // Mark it as being worked on. $this->em->createQueryBuilder() ->update(QueueTask::class, 'qt') From 22764f3c597ac9ba3627303c7fc4b14cb887fadb Mon Sep 17 00:00:00 2001 From: Tobias Werth Date: Sat, 23 Nov 2024 14:11:03 +0100 Subject: [PATCH 21/23] Remove transaction from fetch work API. We have seen the transaction to fail, resulting in exceptions/500s. There is also no need to have a transaction at all. We now do check after the update whether we won instead and if not, tell the judgehost to try again. (cherry picked from commit c06ee8acae7a27a898a5b018cc52ca7e98c44f60) --- .../Controller/API/JudgehostController.php | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index a644a944c9..9d2e21ee20 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1583,40 +1583,42 @@ public function getJudgeTasksAction(Request $request): array // This is case 2.a) from above: start something new. // This runs transactional to prevent a queue task being picked up twice. $judgetasks = null; - $this->em->wrapInTransaction(function () use ($judgehost, $max_batchsize, &$judgetasks) { - $jobid = $this->em->createQueryBuilder() - ->from(QueueTask::class, 'qt') - ->innerJoin('qt.judging', 'j') - ->select('j.judgingid') + $jobid = $this->em->createQueryBuilder() + ->from(QueueTask::class, 'qt') + ->innerJoin('qt.judging', 'j') + ->select('j.judgingid') + ->andWhere('qt.startTime IS NULL') + ->addOrderBy('qt.priority') + ->addOrderBy('qt.teamPriority') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR); + if ($jobid !== null) { + // Mark it as being worked on. + $result = $this->em->createQueryBuilder() + ->update(QueueTask::class, 'qt') + ->set('qt.startTime', Utils::now()) + ->andWhere('qt.judging = :jobid') ->andWhere('qt.startTime IS NULL') - ->addOrderBy('qt.priority') - ->addOrderBy('qt.teamPriority') - ->setMaxResults(1) + ->setParameter('jobid', $jobid) ->getQuery() - ->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR); - if ($jobid === null) { - return; - } - $judgetasks = $this->getJudgetasks($jobid, $max_batchsize, $judgehost); - if (empty($judgetasks)) { - // Somehow we got ourselves in a situation that there was a queue task without remaining judge tasks. - // This should not happen, but if it does, we need to clean up. Each of the fetch-work calls will clean - // up one queue task. We need to signal to the judgehost that there might be more work to do. + ->execute(); + + if ($result == 0) { + // Another judgehost beat us to it. $judgetasks = [['type' => 'try_again']]; } else { - // Mark it as being worked on. - $this->em->createQueryBuilder() - ->update(QueueTask::class, 'qt') - ->set('qt.startTime', Utils::now()) - ->andWhere('qt.judging = :jobid') - ->andWhere('qt.startTime IS NULL') - ->setParameter('jobid', $jobid) - ->getQuery() - ->execute(); + $judgetasks = $this->getJudgetasks($jobid, $max_batchsize, $judgehost); + if (empty($judgetasks)) { + // Somehow we got ourselves in a situation that there was a queue task without remaining judge tasks. + // This should not happen, but if it does, we need to clean up. Each of the fetch-work calls will clean + // up one queue task. We need to signal to the judgehost that there might be more work to do. + $judgetasks = [['type' => 'try_again']]; + } + } + if (!empty($judgetasks)) { + return $judgetasks; } - }); - if (!empty($judgetasks)) { - return $judgetasks; } if ($this->config->get('enable_parallel_judging')) { From edab328e209e2bc364e6d82ed40e4e85b6b644ce Mon Sep 17 00:00:00 2001 From: Jaap Eldering Date: Sat, 23 Nov 2024 15:13:01 +0100 Subject: [PATCH 22/23] Don't try to update aborted judgings This (typically?) happens while cancelling a rejudging while there are judgedaemons actively judging. Fixes errors like ``` [Nov 23 13:34:53.075] judgedaemon[526392]: warning: Error while executing curl POST to url http://localhost/domjudge/api/judgehosts/add-judging-run/tiger-1/1555747: http status code: 500, request size = 67745, response: array ( 'code' => 500, 'message' => 'internal bug: the evaluated result changed during judging', 'class' => 'BadMethodCallException', 'trace' => array ( 0 => array ( 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => '/home/jaap/domjudge/git/domjudge/webapp/src/Controller/API/JudgehostController.php', 'line' => 1053, 'args' => array ( ), ), ... ``` (cherry picked from commit cb37af5abd111b48780b4875866fb93f521ecb27) --- webapp/src/Controller/API/JudgehostController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 9d2e21ee20..08bc208329 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1032,6 +1032,12 @@ private function addSingleJudgingRun( // Only update if the current result is different from what we had before. // This should only happen when the old result was NULL. if ($oldResult !== $result) { + if ($oldResult === 'aborted') { + // This judging was cancelled while we worked on it, + // probably as part of a cancelled rejudging. + // Throw away our work, and return that we're done. + return false; + } if ($oldResult !== null) { throw new BadMethodCallException('internal bug: the evaluated result changed during judging'); } From 296242d12e93a1a9029e7870de9586098616a556 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Sat, 23 Nov 2024 15:39:08 +0100 Subject: [PATCH 23/23] Convert whitesmoke to hex before parsing it This fixes many deprecation errors on the mobile scoreboard. (cherry picked from commit bb76cedc05049535e206750036994172d37516a5) --- webapp/src/Twig/TwigExtension.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index a496f540be..3a44fccb52 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -1065,9 +1065,9 @@ public function fileTypeIcon(string $type): string public function problemBadge(ContestProblem $problem, bool $grayedOut = false): string { - $rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff'); + $rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff'); if ($grayedOut) { - $rgb = 'whitesmoke'; + $rgb = Utils::convertToHex('whitesmoke'); } $background = Utils::parseHexColor($rgb); @@ -1095,9 +1095,9 @@ public function problemBadge(ContestProblem $problem, bool $grayedOut = false): public function problemBadgeMaybe(ContestProblem $problem, ScoreboardMatrixItem $matrixItem): string { - $rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff'); + $rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff'); if (!$matrixItem->isCorrect) { - $rgb = 'whitesmoke'; + $rgb = Utils::convertToHex('whitesmoke'); } $background = Utils::parseHexColor($rgb);