From d44c9b393edeb9c64a40fcb0f5107a74b1fb5863 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 26 Jul 2024 15:02:57 +0200 Subject: [PATCH] Introduce way to generated both honors and ranked results --- .../Jury/ImportExportController.php | 142 +++++++++++------- webapp/src/Form/Type/ExportResultsType.php | 78 ++++++++++ webapp/src/Service/ImportExportService.php | 23 ++- .../templates/jury/clarifications.html.twig | 2 +- .../templates/jury/export/results.html.twig | 10 +- webapp/templates/jury/import_export.html.twig | 96 +++++------- .../Jury/ImportExportControllerTest.php | 101 ++++++++----- ...sults-full.tsv => results-full-honors.tsv} | 0 .../Unit/Fixtures/results-full-ranked.tsv | 131 ++++++++++++++++ .../{results-wf.tsv => results-wf-honors.tsv} | 0 .../tests/Unit/Fixtures/results-wf-ranked.tsv | 131 ++++++++++++++++ .../Unit/Service/ImportExportServiceTest.php | 62 ++++---- 12 files changed, 576 insertions(+), 200 deletions(-) create mode 100644 webapp/src/Form/Type/ExportResultsType.php rename webapp/tests/Unit/Fixtures/{results-full.tsv => results-full-honors.tsv} (100%) create mode 100644 webapp/tests/Unit/Fixtures/results-full-ranked.tsv rename webapp/tests/Unit/Fixtures/{results-wf.tsv => results-wf-honors.tsv} (100%) create mode 100644 webapp/tests/Unit/Fixtures/results-wf-ranked.tsv diff --git a/webapp/src/Controller/Jury/ImportExportController.php b/webapp/src/Controller/Jury/ImportExportController.php index db17ec5964e..f8c28b07a10 100644 --- a/webapp/src/Controller/Jury/ImportExportController.php +++ b/webapp/src/Controller/Jury/ImportExportController.php @@ -11,6 +11,7 @@ use App\Entity\TeamCategory; use App\Form\Type\ContestExportType; use App\Form\Type\ContestImportType; +use App\Form\Type\ExportResultsType; use App\Form\Type\ICPCCmsType; use App\Form\Type\JsonImportType; use App\Form\Type\ProblemsImportType; @@ -46,6 +47,7 @@ use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Twig\Environment; #[Route(path: '/jury/import-export')] #[IsGranted('ROLE_JURY')] @@ -63,6 +65,7 @@ public function __construct( KernelInterface $kernel, #[Autowire('%domjudge.version%')] protected readonly string $domjudgeVersion, + protected readonly Environment $twig, ) { parent::__construct($em, $eventLogService, $dj, $kernel); } @@ -257,21 +260,73 @@ public function indexAction(Request $request): Response return $this->redirectToRoute('jury_import_export'); } - /** @var TeamCategory[] $teamCategories */ - $teamCategories = $this->em->createQueryBuilder() - ->from(TeamCategory::class, 'c', 'c.categoryid') - ->select('c.sortorder, c.name') - ->where('c.visible = 1') - ->orderBy('c.sortorder') - ->getQuery() - ->getResult(); - $sortOrders = []; - foreach ($teamCategories as $teamCategory) { - $sortOrder = $teamCategory['sortorder']; - if (!array_key_exists($sortOrder, $sortOrders)) { - $sortOrders[$sortOrder] = []; + $exportResultsForm = $this->createForm(ExportResultsType::class); + + $exportResultsForm->handleRequest($request); + + if ($exportResultsForm->isSubmitted() && $exportResultsForm->isValid()) { + $contest = $this->dj->getCurrentContest(); + if ($contest === null) { + throw new BadRequestHttpException('No current contest'); } - $sortOrders[$sortOrder][] = $teamCategory['name']; + + $data = $exportResultsForm->getData(); + $format = $data['format']; + $sortOrder = $data['sortorder']; + $individuallyRanked = $data['individually_ranked']; + $honors = $data['honors']; + + $extension = match ($format) { + 'html_inline', 'html_download' => 'html', + 'tsv' => 'tsv', + default => throw new BadRequestHttpException('Invalid format'), + }; + $contentType = match ($format) { + 'html_inline' => 'text/html', + 'html_download' => 'text/html', + 'tsv' => 'text/csv', + default => throw new BadRequestHttpException('Invalid format'), + }; + $contentDisposition = match ($format) { + 'html_inline' => 'inline', + 'html_download', 'tsv' => 'attachment', + default => throw new BadRequestHttpException('Invalid format'), + }; + $filename = 'results.' . $extension; + + $response = new StreamedResponse(); + $response->setCallback(function () use ( + $format, + $sortOrder, + $individuallyRanked, + $honors + ) { + if ($format === 'tsv') { + $data = $this->importExportService->getResultsData( + $sortOrder->sort_order, + $individuallyRanked, + $honors, + ); + + echo "results\t1\n"; + foreach ($data as $row) { + echo implode("\t", array_map(fn($field) => Utils::toTsvField((string)$field), $row->toArray())) . "\n"; + } + } else { + echo $this->getResultsHtml( + $sortOrder->sort_order, + $individuallyRanked, + $honors, + ); + } + }); + $response->headers->set('Content-Type', $contentType); + $response->headers->set('Content-Disposition', "$contentDisposition; filename=\"$filename\""); + $response->headers->set('Content-Transfer-Encoding', 'binary'); + $response->headers->set('Connection', 'Keep-Alive'); + $response->headers->set('Accept-Ranges', 'bytes'); + + return $response; } return $this->render('jury/import_export.html.twig', [ @@ -282,16 +337,13 @@ public function indexAction(Request $request): Response 'contest_export_form' => $contestExportForm, 'contest_import_form' => $contestImportForm, 'problems_import_form' => $problemsImportForm, - 'sort_orders' => $sortOrders, + 'export_results_form' => $exportResultsForm, ]); } #[Route(path: '/export/{type}.tsv', name: 'jury_tsv_export')] - public function exportTsvAction( - string $type, - #[MapQueryParameter(name: 'sort_order')] - ?int $sortOrder, - ): Response { + public function exportTsvAction(string $type): Response + { $data = []; $tsvType = $type; try { @@ -302,14 +354,6 @@ public function exportTsvAction( case 'teams': $data = $this->importExportService->getTeamData(); break; - case 'wf_results': - $data = $this->importExportService->getResultsData($sortOrder); - $tsvType = 'results'; - break; - case 'full_results': - $data = $this->importExportService->getResultsData($sortOrder, full: true); - $tsvType = 'results'; - break; } } catch (BadRequestHttpException $e) { $this->addFlash('danger', $e->getMessage()); @@ -322,9 +366,6 @@ public function exportTsvAction( echo sprintf("%s\t%s\n", $tsvType, $version); foreach ($data as $row) { // Utils::toTsvFields handles escaping of reserved characters. - if ($row instanceof ResultRow) { - $row = $row->toArray(); - } echo implode("\t", array_map(fn($field) => Utils::toTsvField((string)$field), $row)) . "\n"; } }); @@ -335,29 +376,22 @@ public function exportTsvAction( return $response; } - #[Route(path: '/export/{type}.html', name: 'jury_html_export')] - public function exportHtmlAction(Request $request, string $type): Response + #[Route(path: '/export/clarifications.html', name: 'jury_html_export_clarifications')] + public function exportClarificationsHtmlAction(): Response { try { - switch ($type) { - case 'wf_results': - return $this->getResultsHtml($request); - case 'full_results': - return $this->getResultsHtml($request, full: true); - case 'clarifications': - return $this->getClarificationsHtml(); - default: - $this->addFlash('danger', "Unknown export type '" . $type . "' requested."); - return $this->redirectToRoute('jury_import_export'); - } + return $this->getClarificationsHtml(); } catch (BadRequestHttpException $e) { $this->addFlash('danger', $e->getMessage()); return $this->redirectToRoute('jury_import_export'); } } - protected function getResultsHtml(Request $request, bool $full = false): Response - { + protected function getResultsHtml( + int $sortOrder, + bool $individuallyRanked, + bool $honors + ): string { /** @var TeamCategory[] $categories */ $categories = $this->em->createQueryBuilder() ->from(TeamCategory::class, 'c', 'c.categoryid') @@ -392,9 +426,7 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons $regionWinners = []; $rankPerTeam = []; - $sortOrder = $request->query->getInt('sort_order'); - - foreach ($this->importExportService->getResultsData($sortOrder, full: $full) as $row) { + foreach ($this->importExportService->getResultsData($sortOrder, $individuallyRanked, $honors) as $row) { $team = $teamNames[$row->teamId]; $rankPerTeam[$row->teamId] = $row->rank; @@ -421,7 +453,7 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons } if ($row['rank'] === null) { $honorable[] = $row['team']; - } elseif (in_array($row['award'], ['Highest Honors', 'High Honors', 'Honors'], true)) { + } elseif (in_array($row['award'], ['Ranked', 'Highest Honors', 'High Honors', 'Honors'], true)) { $ranked[$row['award']][] = $row; } else { $awarded[] = $row; @@ -432,7 +464,7 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons $collator = new Collator('en_US'); $collator->sort($honorable); - foreach ($ranked as $award => &$rankedTeams) { + foreach ($ranked as &$rankedTeams) { usort($rankedTeams, function (array $a, array $b) use ($collator): int { if ($a['rank'] !== $b['rank']) { return $a['rank'] <=> $b['rank']; @@ -494,16 +526,10 @@ protected function getResultsHtml(Request $request, bool $full = false): Respons 'firstToSolve' => $firstToSolve, 'domjudgeVersion' => $this->domjudgeVersion, 'title' => sprintf('Results for %s', $contest->getName()), - 'download' => $request->query->getBoolean('download'), 'sortOrder' => $sortOrder, ]; - $response = $this->render('jury/export/results.html.twig', $data); - if ($request->query->getBoolean('download')) { - $response->headers->set('Content-disposition', 'attachment; filename=results.html'); - } - - return $response; + return $this->twig->render('jury/export/results.html.twig', $data); } protected function getClarificationsHtml(): Response diff --git a/webapp/src/Form/Type/ExportResultsType.php b/webapp/src/Form/Type/ExportResultsType.php new file mode 100644 index 00000000000..f7fd44a5652 --- /dev/null +++ b/webapp/src/Form/Type/ExportResultsType.php @@ -0,0 +1,78 @@ +em->createQueryBuilder() + ->from(TeamCategory::class, 'c', 'c.categoryid') + ->select('c.sortorder, c.name') + ->where('c.visible = 1') + ->orderBy('c.sortorder') + ->getQuery() + ->getResult(); + $sortOrders = []; + foreach ($teamCategories as $teamCategory) { + $sortOrder = $teamCategory['sortorder']; + if (!array_key_exists($sortOrder, $sortOrders)) { + $sortOrders[$sortOrder] = new stdClass(); + $sortOrders[$sortOrder]->sort_order = $sortOrder; + $sortOrders[$sortOrder]->categories = []; + } + $sortOrders[$sortOrder]->categories[] = $teamCategory['name']; + } + + $builder->add('sortorder', ChoiceType::class, [ + 'choices' => $sortOrders, + 'group_by' => null, + 'choice_label' => fn(stdClass $sortOrder) => sprintf( + '%d with %d categor%s', + $sortOrder->sort_order, + count($sortOrder->categories), + count($sortOrder->categories) === 1 ? 'y' : 'ies', + ), + 'choice_value' => 'sort_order', + 'choice_attr' => fn(stdClass $sortOrder) => [ + 'data-categories' => json_encode($sortOrder->categories), + ], + 'label' => 'Sort order', + 'help' => '[will be replaced by categories]', + ]); + $builder->add('individually_ranked', ChoiceType::class, [ + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'label' => 'Individually ranked?', + ]); + $builder->add('honors', ChoiceType::class, [ + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'label' => 'Honors?', + ]); + $builder->add('format', ChoiceType::class, [ + 'choices' => [ + 'HTML (display inline)' => 'html_inline', + 'HTML (download)' => 'html_download', + 'TSV' => 'tsv', + ], + 'label' => 'Format', + ]); + $builder->add('export', SubmitType::class, ['icon' => 'fa-download']); + } +} diff --git a/webapp/src/Service/ImportExportService.php b/webapp/src/Service/ImportExportService.php index 726efb96e88..399c1a4c66a 100644 --- a/webapp/src/Service/ImportExportService.php +++ b/webapp/src/Service/ImportExportService.php @@ -457,8 +457,11 @@ public function getTeamData(): array /** * @return ResultRow[] */ - public function getResultsData(int $sortOrder, bool $full = false): array - { + public function getResultsData( + int $sortOrder, + bool $individuallyRanked = false, + bool $honors = true, + ): array { $contest = $this->dj->getCurrentContest(); if ($contest === null) { throw new BadRequestHttpException('No current contest'); @@ -530,18 +533,22 @@ public function getResultsData(int $sortOrder, bool $full = false): array $lowestMedalPoints = $teamScore->numPoints; } elseif ($numPoints >= $median) { // Teams with equally solved number of problems get the same rank unless $full is true. - if (!$full) { + if (!$individuallyRanked) { if (!isset($ranks[$numPoints])) { $ranks[$numPoints] = $rank; } $rank = $ranks[$numPoints]; } - if ($numPoints === $lowestMedalPoints) { - $awardString = 'Highest Honors'; - } elseif ($numPoints === $lowestMedalPoints - 1) { - $awardString = 'High Honors'; + if ($honors) { + if ($numPoints === $lowestMedalPoints) { + $awardString = 'Highest Honors'; + } elseif ($numPoints === $lowestMedalPoints - 1) { + $awardString = 'High Honors'; + } else { + $awardString = 'Honors'; + } } else { - $awardString = 'Honors'; + $awardString = 'Ranked'; } } else { $awardString = 'Honorable'; diff --git a/webapp/templates/jury/clarifications.html.twig b/webapp/templates/jury/clarifications.html.twig index 5f0e8f1b4f0..411917f6685 100644 --- a/webapp/templates/jury/clarifications.html.twig +++ b/webapp/templates/jury/clarifications.html.twig @@ -23,7 +23,7 @@ {%- else %}
- + Print clarifications diff --git a/webapp/templates/jury/export/results.html.twig b/webapp/templates/jury/export/results.html.twig index e4078df7335..3e359ad0b6b 100644 --- a/webapp/templates/jury/export/results.html.twig +++ b/webapp/templates/jury/export/results.html.twig @@ -27,9 +27,15 @@ - {% for award in ['Highest Honors', 'High Honors', 'Honors'] %} + {% for award in ['Ranked', 'Highest Honors', 'High Honors', 'Honors'] %} {% if ranked[award] is defined %} -

{{ award }}

+

+ {% if award == 'Ranked' %} + Other ranked teams + {% else %} + {{ award }} + {% endif %} +

diff --git a/webapp/templates/jury/import_export.html.twig b/webapp/templates/jury/import_export.html.twig index d7be0d78460..0cd8971f40c 100644 --- a/webapp/templates/jury/import_export.html.twig +++ b/webapp/templates/jury/import_export.html.twig @@ -88,74 +88,50 @@

Results

-
-
-
+
-

Export <html>

- +

Export clarifications

+
-
-
-
+
-

Export tab-separated

-
    -
  • - wf_results.tsv: - -
  • -
  • - full_results.tsv: - -
  • -
-
+

Export results

+ {{ form(export_results_form) }} +
{% endblock %} +{% block extrafooter %} + {{ parent() }} + +{% endblock %} diff --git a/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php b/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php index b173d8317de..4ccd4717db5 100644 --- a/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php @@ -23,10 +23,6 @@ public function testIndexBasic(): void self::assertSelectorExists(sprintf('h2:contains("%s")', $section)); } self::assertSelectorExists('div.help-text:contains(\'Create a "Web Services Token"\')'); - - // We've reached the end of the page. - self::assertSelectorExists('li:contains("wf_results.tsv")'); - self::assertSelectorExists('li:contains("full_results.tsv")'); } /** @@ -42,18 +38,6 @@ public function testIndexContestDropdowns(string $contest): void self::assertSelectorExists(sprintf('select#contest_export_contest > option:contains("%s")', $contest)); } - /** - * Test that the expected dynamic items on the index page are present. - * - * @dataProvider provideSortOrders - */ - public function testIndexGeneratedItems(string $sortOrder): void - { - $this->verifyPageResponse('GET', '/jury/import-export', 200); - - self::assertSelectorExists(sprintf('li:contains("for sort order %s")', $sortOrder)); - } - public function provideContests(): Generator { yield ['Demo contest']; @@ -153,16 +137,6 @@ public function provideTsvContents(): Generator { yield ['a:contains("teams.tsv")', 'teams 1 exteam exteam participants Example teamname Utrecht University UU NLD utrecht -']; - yield ['li:contains("wf_results.tsv") a:contains("for sort order 0")', 'results 1 -exteam 1 Gold Medal 0 0 0 Participants -']; - yield ['li:contains("wf_results.tsv") a:contains("for sort order 1")', 'results 1 -']; - yield ['li:contains("full_results.tsv") a:contains("for sort order 0")', 'results 1 -exteam 1 Gold Medal 0 0 0 Participants -']; - yield ['li:contains("full_results.tsv") a:contains("for sort order 1")', 'results 1 ']; yield ['a:contains("groups.tsv")', 'groups 1 self-registered Self-Registered @@ -188,30 +162,81 @@ public function testClarificationsHtmlExport(): void } /** - * Test export of wf_results.html. + * Test export of results.html + * + * @dataProvider provideResultsHtmlExport */ - public function testWfResultsHtmlExport(): void + public function testResultsHtmlExport(bool $individuallyRanked, bool $honors, string $format): void { $this->loadFixture(ClarificationFixture::class); $this->verifyPageResponse('GET', '/jury/import-export', 200); - $link = $this->getCurrentCrawler()->filter('li:contains("wf_results.html") a:contains("for sort order 0")')->link(); - $this->client->click($link); + $this->client->submitForm('export_results_export', [ + 'export_results[sortorder]' => 0, + 'export_results[individually_ranked]' => (int)$individuallyRanked, + 'export_results[honors]' => (int)$honors, + 'export_results[format]' => $format, + ]); self::assertSelectorExists('h1:contains("Results for Demo contest")'); self::assertSelectorExists('th:contains("Example teamname")'); self::assertSelectorExists('th:contains("A: Hello World")'); } + public function provideResultsHtmlExport(): Generator + { + yield [true, true, 'html_inline']; + yield [true, false, 'html_inline']; + yield [false, true, 'html_inline']; + yield [false, true, 'html_inline']; + yield [true, true, 'html_download']; + yield [true, false, 'html_download']; + yield [false, true, 'html_download']; + yield [false, true, 'html_download']; + } + /** - * Test export of full_results.html. + * Test export of results.tsv + * + * @dataProvider provideResultsTsvExport */ - public function testFullResultsHtmlExport(): void - { + public function testResultsTsvExport( + int $sortOrder, + bool $individuallyRanked, + bool $honors, + string $expectedData + ): void { $this->loadFixture(ClarificationFixture::class); $this->verifyPageResponse('GET', '/jury/import-export', 200); - $link = $this->getCurrentCrawler()->filter('li:contains("full_results.html") a:contains("for sort order 0")')->link(); - $this->client->click($link); - self::assertSelectorExists('h1:contains("Results for Demo contest")'); - self::assertSelectorExists('th:contains("Example teamname")'); - self::assertSelectorExists('th:contains("A: Hello World")'); + $this->client->submitForm('export_results_export', [ + 'export_results[sortorder]' => $sortOrder, + 'export_results[individually_ranked]' => (int)$individuallyRanked, + 'export_results[honors]' => (int)$honors, + 'export_results[format]' => 'tsv', + ]); + + static::assertEquals($expectedData, $this->client->getInternalResponse()->getContent()); + } + + public function provideResultsTsvExport(): Generator + { + yield [0, true, true, 'results 1 +exteam 1 Gold Medal 0 0 0 Participants +']; + yield [0, true, false, 'results 1 +exteam 1 Gold Medal 0 0 0 Participants +']; + yield [0, false, true, 'results 1 +exteam 1 Gold Medal 0 0 0 Participants +']; + yield [0, false, true, 'results 1 +exteam 1 Gold Medal 0 0 0 Participants +']; + yield [1, true, true, 'results 1 +']; + yield [1, true, false, 'results 1 +']; + yield [1, false, true, 'results 1 +']; + yield [1, false, true, 'results 1 +']; } } diff --git a/webapp/tests/Unit/Fixtures/results-full.tsv b/webapp/tests/Unit/Fixtures/results-full-honors.tsv similarity index 100% rename from webapp/tests/Unit/Fixtures/results-full.tsv rename to webapp/tests/Unit/Fixtures/results-full-honors.tsv diff --git a/webapp/tests/Unit/Fixtures/results-full-ranked.tsv b/webapp/tests/Unit/Fixtures/results-full-ranked.tsv new file mode 100644 index 00000000000..ee2759be99c --- /dev/null +++ b/webapp/tests/Unit/Fixtures/results-full-ranked.tsv @@ -0,0 +1,131 @@ +results 1 +870679 1 Gold Medal 9 995 216 Northern Eurasia +870257 2 Gold Medal 9 1068 227 Asia East +870678 3 Gold Medal 9 1143 206 +873624 4 Gold Medal 9 1304 292 Europe +870259 5 Silver Medal 9 1524 274 +870260 6 Silver Medal 8 1013 281 +928309 7 Silver Medal 8 1102 230 Asia Pacific +870037 8 Silver Medal 8 1120 268 North America +870583 9 Bronze Medal 8 1121 260 +870584 10 Bronze Medal 8 1424 291 +870051 11 Bronze Medal 7 842 279 +870647 12 Bronze Medal 7 940 259 +870670 13 Ranked 7 955 291 Latin America +870585 14 Ranked 7 962 290 +870649 14 Ranked 7 962 290 +870271 16 Ranked 7 980 283 +870642 17 Ranked 7 1021 256 +870045 18 Ranked 7 1076 271 +870582 19 Ranked 7 1128 278 +870654 20 Ranked 7 1130 284 +868994 21 Ranked 7 1381 296 +870644 22 Ranked 6 510 187 +870646 23 Ranked 6 642 216 +870680 24 Ranked 6 645 218 +881825 25 Ranked 6 680 237 +871349 26 Ranked 6 683 246 +870692 27 Ranked 6 708 243 +870041 28 Ranked 6 718 260 +870268 29 Ranked 6 765 292 +870681 30 Ranked 6 932 287 +870040 31 Ranked 6 968 238 +870044 32 Ranked 6 1010 275 +870658 33 Ranked 6 1046 293 +870038 34 Ranked 6 1103 282 +870696 35 Ranked 6 1189 290 +870650 36 Ranked 5 398 137 +870672 37 Ranked 5 489 158 +870656 38 Ranked 5 496 116 +870043 39 Ranked 5 522 160 +870648 40 Ranked 5 573 168 +870652 41 Ranked 5 578 143 +870627 42 Ranked 5 579 180 Asia West +870639 43 Ranked 5 582 213 +870273 44 Ranked 5 592 199 +870653 45 Ranked 5 630 292 +870659 46 Ranked 5 644 154 +870683 47 Ranked 5 653 207 +870874 48 Ranked 5 660 221 +870052 49 Ranked 5 662 181 +870270 50 Ranked 5 683 239 +870046 51 Ranked 5 737 227 +870050 52 Ranked 5 739 260 +870637 53 Ranked 5 742 255 +870048 54 Ranked 5 743 271 +870630 55 Ranked 5 747 247 +870272 56 Ranked 5 747 284 +870667 57 Ranked 5 770 216 +870686 58 Ranked 5 795 219 +870578 59 Ranked 5 807 257 +870579 60 Ranked 5 822 205 +870267 61 Ranked 5 833 257 +870674 62 Ranked 5 837 226 +870691 63 Ranked 5 839 243 +870264 64 Ranked 5 850 209 +870635 65 Ranked 5 862 275 +870590 66 Ranked 5 867 245 +870269 67 Ranked 5 878 267 +870668 68 Ranked 5 889 257 +870263 69 Ranked 5 891 220 +870065 70 Ranked 5 908 238 Africa and Arab +870053 71 Ranked 5 968 260 +870042 72 Ranked 5 971 292 +870689 73 Ranked 5 1008 298 +870685 74 Ranked 5 1048 267 +870638 75 Ranked 5 1164 294 +871379 76 Ranked 5 1227 273 +870056 Honorable 2 465 299 +870055 Honorable 4 465 164 +870063 Honorable 1 348 288 +870066 Honorable 2 289 173 +870054 Honorable 4 693 255 +870067 Honorable 2 405 259 +870688 Honorable 4 632 198 +870690 Honorable 3 691 271 +870574 Honorable 4 339 128 +870640 Honorable 3 435 195 +870636 Honorable 3 333 130 +870061 Honorable 1 140 140 +871347 Honorable 3 599 287 +870577 Honorable 4 590 215 +870057 Honorable 2 367 253 +870641 Honorable 3 448 243 +870663 Honorable 0 0 0 +870662 Honorable 2 459 238 +870058 Honorable 2 312 196 +870629 Honorable 3 538 299 +870628 Honorable 4 712 298 +870631 Honorable 4 421 191 +870632 Honorable 4 603 266 +870633 Honorable 3 469 250 +870634 Honorable 1 96 96 +870694 Honorable 4 879 290 +870068 Honorable 1 74 74 +870693 Honorable 3 650 279 +870587 Honorable 4 447 228 +870588 Honorable 4 707 244 +873768 Honorable 4 651 210 +870687 Honorable 4 870 268 +870643 Honorable 4 379 192 +870581 Honorable 2 398 287 +870258 Honorable 4 448 141 +870062 Honorable 1 58 58 +869963 Honorable 4 920 274 +870675 Honorable 1 162 162 +870664 Honorable 2 255 226 +870660 Honorable 3 766 289 +870676 Honorable 2 279 150 +870673 Honorable 1 230 190 +870671 Honorable 3 333 196 +870661 Honorable 3 728 272 +870669 Honorable 4 654 210 +870666 Honorable 3 382 177 +870665 Honorable 3 568 224 +870657 Honorable 4 333 107 +870651 Honorable 4 474 160 +870645 Honorable 3 609 277 +870591 Honorable 1 86 66 +870589 Honorable 3 480 178 +870039 Honorable 3 596 252 +870697 Honorable 3 761 275 diff --git a/webapp/tests/Unit/Fixtures/results-wf.tsv b/webapp/tests/Unit/Fixtures/results-wf-honors.tsv similarity index 100% rename from webapp/tests/Unit/Fixtures/results-wf.tsv rename to webapp/tests/Unit/Fixtures/results-wf-honors.tsv diff --git a/webapp/tests/Unit/Fixtures/results-wf-ranked.tsv b/webapp/tests/Unit/Fixtures/results-wf-ranked.tsv new file mode 100644 index 00000000000..ee11ca9453b --- /dev/null +++ b/webapp/tests/Unit/Fixtures/results-wf-ranked.tsv @@ -0,0 +1,131 @@ +results 1 +870679 1 Gold Medal 9 995 216 Northern Eurasia +870257 2 Gold Medal 9 1068 227 Asia East +870678 3 Gold Medal 9 1143 206 +873624 4 Gold Medal 9 1304 292 Europe +870259 5 Silver Medal 9 1524 274 +870260 6 Silver Medal 8 1013 281 +928309 7 Silver Medal 8 1102 230 Asia Pacific +870037 8 Silver Medal 8 1120 268 North America +870583 9 Bronze Medal 8 1121 260 +870584 10 Bronze Medal 8 1424 291 +870051 11 Bronze Medal 7 842 279 +870647 12 Bronze Medal 7 940 259 +870654 13 Ranked 7 1130 284 +870582 13 Ranked 7 1128 278 +870045 13 Ranked 7 1076 271 +870585 13 Ranked 7 962 290 +870670 13 Ranked 7 955 291 Latin America +870649 13 Ranked 7 962 290 +868994 13 Ranked 7 1381 296 +870642 13 Ranked 7 1021 256 +870271 13 Ranked 7 980 283 +870681 22 Ranked 6 932 287 +870038 22 Ranked 6 1103 282 +870044 22 Ranked 6 1010 275 +870268 22 Ranked 6 765 292 +870646 22 Ranked 6 642 216 +870644 22 Ranked 6 510 187 +870040 22 Ranked 6 968 238 +870692 22 Ranked 6 708 243 +870680 22 Ranked 6 645 218 +870658 22 Ranked 6 1046 293 +881825 22 Ranked 6 680 237 +870041 22 Ranked 6 718 260 +870696 22 Ranked 6 1189 290 +871349 22 Ranked 6 683 246 +870635 36 Ranked 5 862 275 +870267 36 Ranked 5 833 257 +870638 36 Ranked 5 1164 294 +870264 36 Ranked 5 850 209 +870648 36 Ranked 5 573 168 +870674 36 Ranked 5 837 226 +870656 36 Ranked 5 496 116 +870065 36 Ranked 5 908 238 Africa and Arab +870269 36 Ranked 5 878 267 +870639 36 Ranked 5 582 213 +870627 36 Ranked 5 579 180 Asia West +870630 36 Ranked 5 747 247 +870668 36 Ranked 5 889 257 +870637 36 Ranked 5 742 255 +870579 36 Ranked 5 822 205 +870685 36 Ranked 5 1048 267 +870650 36 Ranked 5 398 137 +870689 36 Ranked 5 1008 298 +870578 36 Ranked 5 807 257 +870052 36 Ranked 5 662 181 +870691 36 Ranked 5 839 243 +870672 36 Ranked 5 489 158 +870653 36 Ranked 5 630 292 +870263 36 Ranked 5 891 220 +870874 36 Ranked 5 660 221 +870686 36 Ranked 5 795 219 +870683 36 Ranked 5 653 207 +870043 36 Ranked 5 522 160 +870053 36 Ranked 5 968 260 +870659 36 Ranked 5 644 154 +870667 36 Ranked 5 770 216 +871379 36 Ranked 5 1227 273 +870042 36 Ranked 5 971 292 +870050 36 Ranked 5 739 260 +870048 36 Ranked 5 743 271 +870272 36 Ranked 5 747 284 +870270 36 Ranked 5 683 239 +870652 36 Ranked 5 578 143 +870273 36 Ranked 5 592 199 +870590 36 Ranked 5 867 245 +870046 36 Ranked 5 737 227 +870056 Honorable 2 465 299 +870055 Honorable 4 465 164 +870063 Honorable 1 348 288 +870066 Honorable 2 289 173 +870054 Honorable 4 693 255 +870067 Honorable 2 405 259 +870688 Honorable 4 632 198 +870690 Honorable 3 691 271 +870574 Honorable 4 339 128 +870640 Honorable 3 435 195 +870636 Honorable 3 333 130 +870061 Honorable 1 140 140 +871347 Honorable 3 599 287 +870577 Honorable 4 590 215 +870057 Honorable 2 367 253 +870641 Honorable 3 448 243 +870663 Honorable 0 0 0 +870662 Honorable 2 459 238 +870058 Honorable 2 312 196 +870629 Honorable 3 538 299 +870628 Honorable 4 712 298 +870631 Honorable 4 421 191 +870632 Honorable 4 603 266 +870633 Honorable 3 469 250 +870634 Honorable 1 96 96 +870694 Honorable 4 879 290 +870068 Honorable 1 74 74 +870693 Honorable 3 650 279 +870587 Honorable 4 447 228 +870588 Honorable 4 707 244 +873768 Honorable 4 651 210 +870687 Honorable 4 870 268 +870643 Honorable 4 379 192 +870581 Honorable 2 398 287 +870258 Honorable 4 448 141 +870062 Honorable 1 58 58 +869963 Honorable 4 920 274 +870675 Honorable 1 162 162 +870664 Honorable 2 255 226 +870660 Honorable 3 766 289 +870676 Honorable 2 279 150 +870673 Honorable 1 230 190 +870671 Honorable 3 333 196 +870661 Honorable 3 728 272 +870669 Honorable 4 654 210 +870666 Honorable 3 382 177 +870665 Honorable 3 568 224 +870657 Honorable 4 333 107 +870651 Honorable 4 474 160 +870645 Honorable 3 609 277 +870591 Honorable 1 86 66 +870589 Honorable 3 480 178 +870039 Honorable 3 596 252 +870697 Honorable 3 761 275 diff --git a/webapp/tests/Unit/Service/ImportExportServiceTest.php b/webapp/tests/Unit/Service/ImportExportServiceTest.php index 042be4a409c..63958d1d046 100644 --- a/webapp/tests/Unit/Service/ImportExportServiceTest.php +++ b/webapp/tests/Unit/Service/ImportExportServiceTest.php @@ -1167,7 +1167,7 @@ protected function getContest(int|string $cid): Contest /** * @dataProvider provideGetResultsData */ - public function testGetResultsData(bool $full, string $expectedResultsFile): void + public function testGetResultsData(bool $full, bool $honors, string $expectedResultsFile): void { // Set up some results we can test with // This data is based on the ICPC World Finals 47 @@ -1236,47 +1236,41 @@ public function testGetResultsData(bool $full, string $expectedResultsFile): voi $submissionInsertQuery = $em->getConnection()->prepare('INSERT INTO submission (teamid, cid, probid, langid, submittime) VALUES (:teamid, :cid, :probid, :langid, :submittime)'); $judgingInsertQuery = $em->getConnection()->prepare('INSERT INTO judging (uuid, submitid, result) VALUES (:uuid, :submitid, :result)'); + $submissionInsertQuery->bindValue('cid', $contest->getCid()); + $submissionInsertQuery->bindValue('langid', $cpp->getLangid()); + $scoreboardData = json_decode(file_get_contents(__DIR__ . '/../Fixtures/sample-scoreboard.json'), true); foreach ($scoreboardData['rows'] as $scoreboardRow) { $team = $teamsById[$scoreboardRow['team_id']]; + $submissionInsertQuery->bindValue('teamid', $team->getTeamid()); foreach ($scoreboardRow['problems'] as $problemData) { if ($problemData['solved']) { $contestProblem = $contestProblemsById[$problemData['problem_id']]; // Add fake submission for this problem. First add wrong ones for ($i = 0; $i < $problemData['num_judged'] - 1; $i++) { - $submissionInsertQuery->executeQuery([ - 'teamid' => $team->getTeamid(), - 'cid' => $contest->getCid(), - 'probid' => $contestProblem->getProbid(), - 'langid' => $cpp->getLangid(), - 'submittime' => $startTime - ->add(new DateInterval('PT' . $problemData['time'] . 'M')) - ->sub(new DateInterval('PT1M')) - ->getTimestamp(), - ]); + $submissionInsertQuery->bindValue('probid', $contestProblem->getProbid()); + $submissionInsertQuery->bindValue('submittime', $startTime + ->add(new DateInterval('PT' . $problemData['time'] . 'M')) + ->sub(new DateInterval('PT1M')) + ->getTimestamp()); + $submissionInsertQuery->executeQuery(); $submitId = $em->getConnection()->lastInsertId(); - $judgingInsertQuery->executeQuery([ - 'uuid' => Uuid::uuid4()->toString(), - 'submitid' => $submitId, - 'result' => 'wrong-awnser', - ]); + $judgingInsertQuery->bindValue('uuid', Uuid::uuid4()->toString()); + $judgingInsertQuery->bindValue('submitid', $submitId); + $judgingInsertQuery->bindValue('result', 'wrong-awnser'); + $judgingInsertQuery->executeQuery(); } // Add correct submission - $submissionInsertQuery->executeQuery([ - 'teamid' => $team->getTeamid(), - 'cid' => $contest->getCid(), - 'probid' => $contestProblem->getProbid(), - 'langid' => $cpp->getLangid(), - 'submittime' => $startTime - ->add(new DateInterval('PT' . $problemData['time'] . 'M')) - ->getTimestamp(), - ]); + $submissionInsertQuery->bindValue('probid', $contestProblem->getProbid()); + $submissionInsertQuery->bindValue('submittime', $startTime + ->add(new DateInterval('PT' . $problemData['time'] . 'M')) + ->getTimestamp()); + $submissionInsertQuery->executeQuery(); $submitId = $em->getConnection()->lastInsertId(); - $judgingInsertQuery->executeQuery([ - 'uuid' => Uuid::uuid4()->toString(), - 'submitid' => $submitId, - 'result' => 'correct', - ]); + $judgingInsertQuery->bindValue('uuid', Uuid::uuid4()->toString()); + $judgingInsertQuery->bindValue('submitid', $submitId); + $judgingInsertQuery->bindValue('result', 'correct'); + $judgingInsertQuery->executeQuery(); } } } @@ -1294,7 +1288,7 @@ public function testGetResultsData(bool $full, string $expectedResultsFile): voi $request->cookies->set('domjudge_cid', (string)$contest->getCid()); $requestStack->push($request); - $results = $importExportService->getResultsData(37, $full); + $results = $importExportService->getResultsData(37, $full, $honors); $resultsContents = file_get_contents(__DIR__ . '/../Fixtures/' . $expectedResultsFile); $resultsContents = substr($resultsContents, strpos($resultsContents, "\n") + 1); @@ -1312,7 +1306,9 @@ public function testGetResultsData(bool $full, string $expectedResultsFile): voi public function provideGetResultsData(): Generator { - yield [true, 'results-full.tsv']; - yield [false, 'results-wf.tsv']; + yield [true, true, 'results-full-honors.tsv']; + yield [false, true, 'results-wf-honors.tsv']; + yield [true, false, 'results-full-ranked.tsv']; + yield [false, false, 'results-wf-ranked.tsv']; } }