Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output vizualizer #2744

Open
wants to merge 54 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ebb0b43
Fix judgehost check if its enabled
vmcj Oct 13, 2024
a43209a
Future work
Oct 13, 2024
d3eec0c
Store new visualizer type
Oct 13, 2024
cdc1692
Make the doctrine links
Oct 13, 2024
88c0e88
This should be part of the ProblemZip
Oct 13, 2024
131abb5
Add simple output_validator
Oct 13, 2024
5e3406f
Assume this will be put in the spec
Oct 13, 2024
3b40afa
Allow upload of the output_visualizer
Oct 13, 2024
d4f4401
First try on import
Oct 13, 2024
101cc03
Give icon for new executable type
Oct 13, 2024
69d99dc
Display the problem badge
Oct 14, 2024
df61e4a
Display on the executable page itself
Oct 14, 2024
a956b58
Add button to generate the visualization
Oct 13, 2024
fbe7358
Show executable in problem page
Oct 13, 2024
2986727
Leave this for the demo
Oct 14, 2024
d79783d
Remember if we already requested visualization
Oct 14, 2024
8de3e52
Create the needed judgetasks
Oct 14, 2024
f3b96e1
Fix PHPStan issues
Oct 15, 2024
f6baa3a
Fix syntax error
Oct 15, 2024
76deaaf
Just move the whole code away for now
Oct 15, 2024
aa062d7
And more errors as the needed code was commented out
Oct 15, 2024
2963350
Revert later - Fixate hostname for api/doc debugging
Oct 15, 2024
f0dcaec
Fix API for when you test on a deeper dir
Oct 15, 2024
1636944
New migrations
Oct 16, 2024
1af147a
Test the search in an unrelated page
Oct 16, 2024
d88d0c0
Also get the new visualization `JudgeTask`s
Oct 16, 2024
e56a83f
Return the visualizer jobs
Oct 16, 2024
3a05304
Store the judgetask
Oct 15, 2024
87ca226
Working on the judgehost side
Oct 15, 2024
5d5d6c6
Fix output to create the image
Oct 17, 2024
52fde24
Finish
Oct 17, 2024
ac0679c
Add forgotten class
Oct 17, 2024
1edc428
Fix the visualizer script
Oct 17, 2024
9362b92
Cleanup the judgehost file
Oct 17, 2024
812042e
Check returncode as `FIXME`.
Oct 18, 2024
eca9e35
Merge migrations
Oct 18, 2024
de746eb
Cleanup unrelated testing
Oct 18, 2024
70ee0e4
Update webapp/src/Entity/Visualization.php
vmcj Oct 18, 2024
7bcac2b
Update webapp/src/Controller/API/JudgehostController.php
vmcj Oct 18, 2024
0a7daca
Update webapp/migrations/Version20241018061817.php
vmcj Oct 18, 2024
7190795
Update webapp/migrations/Version20241018061817.php
vmcj Oct 18, 2024
c424d21
Update webapp/src/Controller/API/JudgehostController.php
vmcj Oct 18, 2024
fa3e2ed
Update webapp/src/Controller/API/JudgehostController.php
vmcj Oct 18, 2024
7641e17
Update webapp/src/Controller/API/JudgehostController.php
vmcj Oct 18, 2024
5b9c970
Update webapp/src/Controller/Jury/UserController.php
vmcj Oct 18, 2024
031d24a
Remove dump and debug statements
Oct 18, 2024
51d3cc3
Cleanup unused code
Oct 18, 2024
d34fce3
Fix missing `$run`
Oct 18, 2024
d616057
Prevent duplication of code
Oct 18, 2024
ed5e47c
Not used
Oct 18, 2024
f9de23c
Fix W3C test
Oct 18, 2024
f7e7e23
Fixup
Oct 18, 2024
28c3904
Add last forgotten code
Oct 18, 2024
2b6afcd
Fixup
Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3
#
# Invoke as:
# <output_visualizer_program> answer_file feedback_file
import matplotlib.pyplot as plt
import sys

my_name = sys.argv[0]
my_input = sys.argv[1]
my_feedback = sys.argv[2]

with open(my_input, 'r') as f:
lines = f.readlines()
vals = []
for line in lines:
if 'READ' in line:
vals.append(int(line.split(' ')[-1]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks if the submission writes bogus output, e.g. READ domjudge, how do we handle cases like this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is something which is left up to the jury.

I wonder if there is something we can do with the output_validator & the judgemessage for communication. Either an extra exitcode or otherwise the jury has to parse the output in this script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's left to the jury. In this case you are the jury by implementing the visualizer :-)

Since people are going to model after examples we give them, we should make it robust and not crash.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding to this, I think we should return 42 in the script if we were able to visualize the output, and 43 if not.

plt.plot([0,1])
plt.ylabel('Guesses')
plt.savefig(f"{my_feedback}", format='png')
1 change: 1 addition & 0 deletions example_problems/boolfind/problem.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
name: Boolean switch search

validation: custom interactive
visualization: default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that already spec'ed out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is not something which exists in the spec.

Copy link
Member

@meisterT meisterT Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking because I think this should be custom instead of default since there is no default visualizer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any string will do at this moment, would be nice to get @eldering his opinion as I just picked something. Will change it to custom for now.

55 changes: 52 additions & 3 deletions judge/judgedaemon.main.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@ function fetch_executable_internal(
$execrunpath = $execbuilddir . '/run';
$execrunjurypath = $execbuilddir . '/runjury';
if (!is_dir($execdir) || !file_exists($execdeploypath)) {
system('rm -rf ' . dj_escapeshellarg($execdir) . ' ' . dj_escapeshellarg($execbuilddir));
system('rm -rf ' . dj_escapeshellarg($execdir) . ' ' . dj_escapeshellarg($execbuilddir), $retval);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this PR

if ($retval !== 0) {
logmsg(LOG_WARNING, "Deleting '$execdir' or '$execbuilddir' was unsuccessful.");
}
system('mkdir -p ' . dj_escapeshellarg($execbuilddir), $retval);
if ($retval !== 0) {
error("Could not create directory '$execbuilddir'");
Expand Down Expand Up @@ -768,7 +771,7 @@ function fetch_executable_internal(
$judgehosts = request('judgehosts', 'GET');
if ($judgehosts !== null) {
$judgehosts = dj_json_decode($judgehosts);
$judgehost = array_filter($judgehosts, fn($j) => $j['hostname'] === $myhost);
$judgehost = array_values(array_filter($judgehosts, fn($j) => $j['hostname'] === $myhost))[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of your other PR

if (!isset($judgehost['enabled']) || !$judgehost['enabled']) {
logmsg(LOG_WARNING, "Judgehost needs to be enabled in web interface.");
}
Expand Down Expand Up @@ -844,7 +847,9 @@ function fetch_executable_internal(
$debug_cmd = implode(' ', array_map('dj_escapeshellarg',
[$runpath, $workdir, $tmpfile]));
system($debug_cmd, $retval);
// FIXME: check retval
if ($retval !== 0) {
error("Running '$runpath' failed.");
}

request(
sprintf('judgehosts/add-debug-info/%s/%s', urlencode($myhost),
Expand Down Expand Up @@ -872,6 +877,50 @@ function fetch_executable_internal(
continue;
}

if ($type == 'output_visualization') {
if ($lastWorkdir !== null) {
cleanup_judging($lastWorkdir);
$lastWorkdir = null;
}
foreach ($row as $judgeTask) {
if (isset($judgeTask['output_visualizer_script_id'])) {
// Visualization of output which this host judged requested.
$run_config = dj_json_decode($judgeTask['run_config']);
$tmpfile = tempnam(TMPDIR, 'output_visualization_');
[$runpath, $error] = fetch_executable_internal(
$workdirpath,
'output_visualization',
$judgeTask['output_visualizer_script_id'],
$run_config['hash']
);
if (isset($error)) {
// FIXME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix me 😛

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loosely based on what we do for the debug tasks, I'll see if I can merge the shared parts and possibly fix this one.

continue;
}

$teamoutput = $workdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']) . '/1/program.out';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does the /1/ here come from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting the output of the first run, so in other words, this does not work for multipass problems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right - then please add a TODO and consider making the one a local variabled, e.g. $pass = 1; and then using that in the path construction.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would we want to do in this case? I assume we want to visualize actually the last pass?

$visual_cmd = implode(' ', array_map('dj_escapeshellarg',
[$runpath, $teamoutput, $tmpfile]));
system($visual_cmd, $retval);
if ($retval !== 0) {
error("Running '$runpath' failed.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's rather report an internal error like we do in other cases (and not crash judgedaemons)

}

request(
sprintf('judgehosts/add-visual/%s/%s', urlencode($myhost),
urlencode((string)$judgeTask['judgetaskid'])),
'POST',
['visual_output' => rest_encode_file($tmpfile, false),
'testcase_id' => $judgeTask['testcase_id']],
false
);
unlink($tmpfile);

logmsg(LOG_INFO, " ⇡ Uploading visual output of workdir $workdir.");
}
}
continue;
}
$success_file = "$workdir/.uuid_pid";
$expected_uuid_pid = $row[0]['uuid'] . '_' . (string)getmypid();

Expand Down
54 changes: 54 additions & 0 deletions webapp/migrations/Version20241018061817.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241018061817 extends AbstractMigration
{
public function getDescription(): string
{
return 'Allow storing visualization of team output.';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE problem ADD special_output_visualizer VARCHAR(32) DEFAULT NULL COMMENT \'Executable ID (string)\', CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds; defaults to 1 for traditional problems, 2 for multi-pass problems if not specified.\'');
$this->addSql('ALTER TABLE problem ADD CONSTRAINT FK_D7E7CCC819F5352E FOREIGN KEY (special_output_visualizer) REFERENCES executable (execid) ON DELETE SET NULL');
$this->addSql('CREATE INDEX special_output_visualizer ON problem (special_output_visualizer)');
$this->addSql('ALTER TABLE judgetask ADD output_visualizer_script_id INT UNSIGNED DEFAULT NULL COMMENT \'Output visualizer script ID\'');
$this->addSql('ALTER TABLE `judgetask`
MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\', \'output_visualization\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\'');
$this->addSql('ALTER TABLE judging ADD visualization TINYINT(1) DEFAULT 0 NOT NULL COMMENT \'Explicitly requested to visualize the output.\'');
$this->addSql('CREATE TABLE visualization (visualization_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'Visualization ID\', judgingid INT UNSIGNED DEFAULT NULL COMMENT \'Judging ID\', judgehostid INT UNSIGNED DEFAULT NULL COMMENT \'Judgehost ID\', testcaseid INT UNSIGNED DEFAULT NULL COMMENT \'Testcase ID\', filename VARCHAR(255) NOT NULL COMMENT \'Name of the file where we stored the visualization.\', INDEX IDX_E0936C40E0E4FC3E (judgehostid), INDEX IDX_E0936C40D360BB2B (testcaseid), INDEX judgingid (judgingid), PRIMARY KEY(visualization_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'Team output visualization.\' ');
$this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C405D5FEA72 FOREIGN KEY (judgingid) REFERENCES judging (judgingid) ON DELETE CASCADE');
$this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40E0E4FC3E FOREIGN KEY (judgehostid) REFERENCES judgehost (judgehostid) ON DELETE SET NULL');
$this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40D360BB2B FOREIGN KEY (testcaseid) REFERENCES testcase (testcaseid) ON DELETE CASCADE');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C405D5FEA72');
$this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40E0E4FC3E');
$this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40D360BB2B');
$this->addSql('DROP TABLE visualization');
$this->addSql('ALTER TABLE judging DROP visualization');
$this->addSql('ALTER TABLE `judgetask`
MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\'');
$this->addSql('ALTER TABLE judgetask DROP output_visualizer_script_id');
$this->addSql('ALTER TABLE problem DROP FOREIGN KEY FK_D7E7CCC819F5352E');
$this->addSql('DROP INDEX special_output_visualizer ON problem');
$this->addSql('ALTER TABLE problem DROP special_output_visualizer, CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds for multi-pass problems; defaults to 2 if not specified.\'');
}

public function isTransactional(): bool
{
return false;
}
}
91 changes: 88 additions & 3 deletions webapp/src/Controller/API/JudgehostController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace App\Controller\API;

use App\Entity\Visualization;
use App\Entity\Testcase;

use App\DataTransferObject\JudgehostFile;
use App\Doctrine\DBAL\Types\JudgeTaskType;
use App\Entity\Contest;
Expand Down Expand Up @@ -550,6 +553,58 @@ public function addDebugInfo(
$this->em->flush();
}

/**
* Add visual team output.
*/
#[IsGranted('ROLE_JUDGEHOST')]
#[Rest\Post('/add-visual/{hostname}/{judgeTaskId<\d+>}')]
#[OA\Response(response: 200, description: 'When the visual output has been added')]
public function addVisualization(
Request $request,
#[OA\PathParameter(description: 'The hostname of the judgehost that wants to add the debug info')]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is not debug info

string $hostname,
#[OA\PathParameter(description: 'The ID of the judgetask to add', schema: new OA\Schema(type: 'integer'))]
int $judgeTaskId
): void {
$judgeTask = $this->em->getRepository(JudgeTask::class)->find($judgeTaskId);
if ($judgeTask === null) {
throw new BadRequestHttpException(
'Inconsistent data, no judgetask known with judgetaskid = ' . $judgeTaskId . '.');
}

foreach (['visual_output', 'testcase_id'] as $argument) {
if (!$request->request->has($argument)) {
throw new BadRequestHttpException(
sprintf("Argument '%s' is mandatory", $argument));
}
}

$judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]);
if (!$judgehost) {
throw new BadRequestHttpException("Who are you and why are you sending us any data?");
}

$judging = $this->em->getRepository(Judging::class)->find($judgeTask->getJobId());
if ($judging === null) {
throw new BadRequestHttpException(
'Inconsistent data, no judging known with judgingid = ' . $judgeTask->getJobId() . '.');
}
if ($tempFilename = tempnam($this->dj->getDomjudgeTmpDir(), "visual-")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means it doesn't work with 2 servers in HA mode. We should store this in the DB as a file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is loosely based on what we do with debug packages, so we have the same problem there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I didn't know. Then maybe for now that's fine but we should fix it at some point?

$debug_package = base64_decode($request->request->get('visual_output'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: update name (no longer debug_package)

file_put_contents($tempFilename, $debug_package);
}
// FIXME: error checking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another fixme

$testcase = $this->em->getRepository(Testcase::class)->findOneBy(['testcaseid' => $request->request->get('testcase_id')]);
$visualization = new Visualization();
$visualization
->setJudgehost($judgehost)
->setJudging($judging)
->setTestcase($testcase)
->setFilename($tempFilename);
$this->em->persist($visualization);
$this->em->flush();
}

/**
* Add one JudgingRun. When relevant, finalize the judging.
* @throws DBALException
Expand Down Expand Up @@ -1186,7 +1241,7 @@ public function getFilesAction(
return match ($type) {
'source' => $this->getSourceFiles($id),
'testcase' => $this->getTestcaseFiles($id),
'compare', 'compile', 'debug', 'run' => $this->getExecutableFiles($id),
'compare', 'compile', 'debug', 'run', 'output_visualization' => $this->getExecutableFiles($id),
default => throw new BadRequestHttpException('Unknown type requested.'),
};
}
Expand Down Expand Up @@ -1478,6 +1533,7 @@ public function getJudgeTasksAction(Request $request): array
throw new BadRequestHttpException('Argument \'hostname\' is mandatory');
}
$hostname = $request->request->get('hostname');
$hostname = 'Computer';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops


$judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]);
if (!$judgehost) {
Expand Down Expand Up @@ -1649,6 +1705,27 @@ public function getJudgeTasksAction(Request $request): array
return $this->serializeJudgeTasks($judgetasks, $judgehost);
}

// If there is nothing else, get visualization jobs that are assigned to this host.
/** @var JudgeTask[] $judgetasks */
$judgetasks = $this->em
->createQueryBuilder()
->from(JudgeTask::class, 'jt')
->select('jt')
->andWhere('jt.judgehost = :judgehost OR jt.judgehost IS NULL')
//->andWhere('jt.starttime IS NULL')
->andWhere('jt.valid = 1')
->andWhere('jt.type = :type')
->setParameter('judgehost', $judgehost)
->setParameter('type', JudgeTaskType::OUTPUT_VISUALIZATION)
->addOrderBy('jt.priority')
->addOrderBy('jt.judgetaskid')
->setMaxResults(1)
->getQuery()
->getResult();
if (!empty($judgetasks)) {
return $this->serializeJudgeTasks($judgetasks, $judgehost);
}

return [];
}

Expand All @@ -1667,8 +1744,16 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a
$submit_id = $judgeTasks[0]->getSubmission()?->getSubmitid();
$judgetaskids = [];
foreach ($judgeTasks as $judgeTask) {
if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) {
$judgetaskids[] = $judgeTask->getJudgetaskid();
if ($judgeTask->getType() == 'judging_run') {
if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) {
$judgetaskids[] = $judgeTask->getJudgetaskid();
}
} else {
// Just pick everything assigned to the judgehost itself or unassigned for now
$assignedJudgehost = $judgeTask->getJudgehost();
if ($assignedJudgehost === $judgehost || $assignedJudgehost === null) {
$judgetaskids[] = $judgeTask->getJudgetaskid();
}
}
}

Expand Down
14 changes: 10 additions & 4 deletions webapp/src/Controller/Jury/ExecutableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,28 @@ public function indexAction(Request $request): Response
->join('cp.problem', 'p')
->leftJoin('p.compare_executable', 'ecomp')
->leftJoin('p.run_executable', 'erun')
->andWhere('ecomp IS NOT NULL OR erun IS NOT NULL')
->leftJoin('p.output_visualizer_executable', 'evisual')
->andWhere('ecomp IS NOT NULL OR erun IS NOT NULL OR evisual IS NOT NULL')
->getQuery()->getResult();
$executablesWithContestProblems = $em->createQueryBuilder()
->select('e')
->from(Executable::class, 'e')
->leftJoin('e.problems_compare', 'pcomp')
->leftJoin('e.problems_run', 'prun')
->where('pcomp IS NOT NULL OR prun IS NOT NULL')
->leftJoin('e.problems_output_visualizer', 'pvisual')
->where('pcomp IS NOT NULL OR prun IS NOT NULL OR pvisual IS NOT NULL')
->leftJoin('pcomp.contest_problems', 'cpcomp')
->leftJoin('prun.contest_problems', 'cprun')
->andWhere('cprun.contest = :contest OR cpcomp.contest = :contest')
->leftJoin('pvisual.contest_problems', 'cpvisual')
->andWhere('cprun.contest = :contest OR cpcomp.contest = :contest OR cpvisual.contest = :contest')
->setParameter('contest', $this->dj->getCurrentContest())
->getQuery()->getResult();
}

foreach ($executables as $e) {
$badges = [];
if (in_array($e, $executablesWithContestProblems)) {
foreach (array_merge($e->getProblemsRun()->toArray(), $e->getProblemsCompare()->toArray()) as $execProblem) {
foreach (array_merge($e->getProblemsRun()->toArray(), $e->getProblemsCompare()->toArray(), $e->getProblemsOutputVisualizer()->toArray()) as $execProblem) {
$execContestProblems = $execProblem->getContestProblems();
foreach ($contestProblemsWithExecutables as $cp) {
if ($execContestProblems->contains($cp)) {
Expand Down Expand Up @@ -142,6 +145,9 @@ public function indexAction(Request $request): Response
case 'run':
$execdata['icon']['icon'] = 'person-running';
break;
case 'output_visualizer':
$execdata['icon']['icon'] = 'paint-brush';
break;
default:
$execdata['icon']['icon'] = 'question';
}
Expand Down
1 change: 1 addition & 0 deletions webapp/src/Controller/Jury/JudgeRemainingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protected function judgeRemaining(array $judgings): void
$numRequested = $this->em->getConnection()->executeStatement(
'UPDATE judgetask SET valid=1'
. ' WHERE jobid=:jobid'
. ' AND type="judging_run"'
. ' AND judgehostid IS NULL',
[
'jobid' => $judgingId,
Expand Down
Loading
Loading