Skip to content

Commit

Permalink
Update ScoreboardMergeCommand: allow reading local files; string grou…
Browse files Browse the repository at this point in the history
…p ids; better problem matching
  • Loading branch information
RagnarGrootKoerkamp committed Sep 23, 2023
1 parent f9ccdfd commit d1df19c
Showing 1 changed file with 60 additions and 33 deletions.
93 changes: 60 additions & 33 deletions webapp/src/Command/ScoreboardMergeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ public function __construct(

protected function configure(): void
{
$this
$this->setName('scoreboard:merge')
->setDescription(
'Merges scoreboards from multiple sites from API endpoints.'
)
->setHelp(
'Usage example: scoreboard:merge "BAPC preliminaries" ' .
'https://judge.gehack.nl/api/v4/contests/3/ 3 ' .
Expand Down Expand Up @@ -89,10 +92,29 @@ protected function configure(): void
'Alternating URL location of the scoreboard to merge and a comma separated list of group_ids to include.' . PHP_EOL .
'If an URL and it requires authentication, use username:password@ in the URL' . PHP_EOL .
'URL should have the form https://<domain>/api/v4/contests/<contestid>/ for DOMjudge or point to any ICPC Contest API compatible contest' . PHP_EOL .
'Only the /teams, /organizations, /problems and /scoreboard endpoint are used, so manually putting files in those locations can work as well.'
'Only the /teams, /organizations, /problems and /scoreboard endpoint are used, so manually putting files in those locations can work as well.' . PHP_EOL .
'Alternatively, you can mount local files directly in the container: add "- /path/to/scoreboards:/scoreboards" to "docker-compose.yml" and use "/scoreboards/eindhoven" as path.'
);
}

/**
* url: "https://judge.gehack.nl/api/v4" or "/path/to/file"
* endpoint: "/teams"
* args: "?public=1" (ignored for files)
*/
protected function getEndpoint(

Check failure on line 105 in webapp/src/Command/ScoreboardMergeCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Method App\Command\ScoreboardMergeCommand::getEndpoint() has no return type specified.
string $url,
string $endpoint,
string $args = ''
) {
if (str_starts_with($url, 'http')) {
return $this->client
->request('GET', $url . $endpoint . $args)
->toArray();
}
return json_decode(file_get_contents($url . $endpoint . '.json'), true);
}

/**
* @throws ClientExceptionInterface
* @throws DecodingExceptionInterface
Expand All @@ -109,7 +131,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$teams = [];
$nextTeamId = 0;
$problems = [];
$problemIdMap = [];
$problemNameToIdMap = [];
$scoreCache = [];
$affiliations = [];
$firstSolve = [];
Expand Down Expand Up @@ -137,13 +159,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$site['path'] = $siteArguments[$i];
# Some simple validation to make sure we're actually parsing group ids.
$groupsString = $siteArguments[$i + 1];
if (!preg_match('/^\d+(,\d+)*$/', $groupsString)) {
$style->error('Argument does not look like a comma separated list of group ids: ' . $groupsString);
return Command::FAILURE;
}
$site['group_ids'] = array_map(
'intval', explode(',', $groupsString)
);
$site['group_ids'] = explode(',', $groupsString);
$sites[] = $site;
}

Expand All @@ -154,12 +170,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$path = substr($path, 0, strlen($path) - 1);
}

$teamData = $this->client
->request('GET', $path . '/teams')->toArray();
$organizationData = $this->client
->request('GET', $path . '/organizations')->toArray();
$problemData = $this->client
->request('GET', $path . '/problems')->toArray();
$teamData = $this->getEndpoint($path, '/teams');
$organizationData = $this->getEndpoint($path, '/organizations');
$problemData = $this->getEndpoint($path, '/problems');
$organizationMap = [];
foreach ($organizationData as $organization) {
$organizationMap[$organization['id']] = $organization;
Expand All @@ -186,6 +199,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$teamObj = (new Team())
->setName($team['name'])
->setDisplayName($team['display_name'] ?? $team['name'])
->setEnabled(true);
if ($team['organization_id'] !== null &&
isset($organizationMap[$team['organization_id']])) {
Expand All @@ -209,19 +223,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$teamIdMap[$oldid] = $newid;
}

$scoreboardData = $this->client
->request('GET', $path . '/scoreboard?public=1')
->toArray();
$scoreboardData = $this->getEndpoint(
$path,
'/scoreboard',
'?public=1'
);

if (!$contest->getStarttimeString()) {
$state = $scoreboardData['state'];
$endtime = $state['ended'] ?? $state['started'];
// While the contest is running, simply use the start time for everything.
$contest
->setStarttimeString($state['started'])
->setEndtimeString($state['ended'])
->setFreezetimeString($state['ended'])
->setUnfreezetimeString($state['ended'])
->setFinalizetime($state['ended'])
->setDeactivatetimeString($state['ended'])
->setEndtimeString($endtime)
->setFreezetimeString($endtime)
->setUnfreezetimeString($endtime)
->setFinalizetime($endtime)
->setDeactivatetimeString($endtime)
->updateTimes();
}
$freezeData = new FreezeData($contest);
Expand All @@ -235,21 +253,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$team = $teams[$teamIdMap[$row['team_id']]];
foreach ($row['problems'] as $problem) {
// Problems are keyed by name, as that seems to be more consistent.
// Some sites occasionally mix up short_name and id.
$problemId = $problem['problem_id'];
$label = $problemMap[$problemId]['label'];
if (!array_key_exists($label, $problemIdMap)) {
$baseProblem = $problemMap[$problemId];
$label = $baseProblem['label'];
$name = $baseProblem['name'];
if (!array_key_exists($name, $problemNameToIdMap)) {
dump("New NAME $name");
$id = count($problems);
$problemObj = (new Problem())
->setProbid($id)
->setName($label);
->setName($name);
$contestProblemObj = (new ContestProblem())
->setProblem($problemObj)
->setColor($baseProblem['color'])
->setShortName($label);
$problems[$id] = $contestProblemObj;
$problemIdMap[$label] = $id;
$firstSolve[$label] = null;
$problemNameToIdMap[$name] = $id;
$firstSolve[$name] = null;
} else {
$id = $problemIdMap[$label];
$id = $problemNameToIdMap[$name];
}
$scoreCacheObj = (new scoreCache())
->setProblem($problems[$id]->getProblem())
Expand All @@ -259,8 +283,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$scoreCacheObj
->setSolveTimePublic($problem['time'] * 60)
->setSolveTimeRestricted($problem['time'] * 60);
if ($firstSolve[$label] === null or $problem['time'] * 60 < $firstSolve[$label]) {
$firstSolve[$label] = $problem['time'] * 60;
if (

Check failure on line 286 in webapp/src/Command/ScoreboardMergeCommand.php

View workflow job for this annotation

GitHub Actions / phpcs

Expected 0 spaces after opening bracket; newline found
$firstSolve[$name] === null or
$problem['time'] * 60 < $firstSolve[$name]
) {
$firstSolve[$name] = $problem['time'] * 60;
}
}
$scoreCacheObj
Expand Down Expand Up @@ -297,7 +324,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
null, null, '', false, true, true, $contest, $scoreboard
);
$data['hide_menu'] = true;
$data['current_public_contest'] = $contest;
$data['current_contest'] = $contest;

$output = $this->twig->render('public/scoreboard.html.twig', $data);
// What files to add to the ZIP file
Expand Down

0 comments on commit d1df19c

Please sign in to comment.