From 08aaee6ec8615dc7246657b6370f5fc0ab184532 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sat, 5 Oct 2024 02:07:58 +0800 Subject: [PATCH] refactor: Optimize pass rate calculation --- config/services.yaml | 1 + src/Entity/Question.php | 49 -------------- src/Repository/SolutionEventRepository.php | 30 +++++++++ src/Service/PassRateService.php | 36 ++++++++++ src/Service/Types/PassRate.php | 67 +++++++++++++++++++ src/Twig/Components/Challenge/Header.php | 8 +++ src/Twig/Components/Questions/Card.php | 23 +++---- .../components/Challenge/Header.html.twig | 5 +- templates/components/Questions/Card.html.twig | 3 +- 9 files changed, 157 insertions(+), 65 deletions(-) create mode 100644 src/Service/PassRateService.php create mode 100644 src/Service/Types/PassRate.php diff --git a/config/services.yaml b/config/services.yaml index 8de680d..b4f0f26 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -23,6 +23,7 @@ services: - "../src/Entity/" - "../src/Kernel.php" - "../src/Service/Processes/" + - "../src/Service/Types/" - "../src/Twig/Components/Challenge/EventConstant.php" # add more service definitions when explicit configuration is needed diff --git a/src/Entity/Question.php b/src/Entity/Question.php index 83dda74..3474022 100644 --- a/src/Entity/Question.php +++ b/src/Entity/Question.php @@ -176,55 +176,6 @@ public function setSolutionVideo(?string $solution_video): static return $this; } - /** - * Get the pass rate of the question. - * - * @param Group|null $group the group to filter the attempts by (null = no group) - * - * @return float the pass rate of the question - */ - public function getPassRate(?Group $group): float - { - $totalAttemptCount = $this->getTotalAttemptCount($group); - if (0 === $totalAttemptCount) { - return 0; - } - - return round($this->getTotalSolvedCount($group) / $totalAttemptCount * 100, 2); - } - - /** - * Get the total number of attempts made on the question. - * - * @param Group|null $group the group to filter the attempts by (null = no group) - * - * @return int the total number of attempts made on the question - */ - public function getTotalAttemptCount(?Group $group): int - { - return $this->getSolutionEvents() - ->filter(fn (SolutionEvent $solutionEvent) => $group === $solutionEvent->getSubmitter()?->getGroup()) - ->count(); - } - - /** - * Get the total number of times the question has been solved. - * - * @param Group|null $group the group to filter the attempts by (null = no group) - * - * @return int the total number of times the question has been solved - */ - public function getTotalSolvedCount(?Group $group): int - { - return $this->getSolutionEvents() - ->filter( - fn (SolutionEvent $solutionEvent) => ( - SolutionEventStatus::Passed === $solutionEvent->getStatus() - && $group === $solutionEvent->getSubmitter()?->getGroup() - ) - )->count(); - } - /** * @return Collection */ diff --git a/src/Repository/SolutionEventRepository.php b/src/Repository/SolutionEventRepository.php index bc5d6ea..61b55b1 100644 --- a/src/Repository/SolutionEventRepository.php +++ b/src/Repository/SolutionEventRepository.php @@ -189,4 +189,34 @@ public function listLeaderboard(?Group $group, string $interval): array return $leaderboard; } + + /** + * Get the total attempts made on the question. + * + * @param Question $question the question to query + * @param Group|null $group the group to filter the attempts by (null = no group) + * + * @return SolutionEvent[] the total attempts made on the question + */ + public function getTotalAttempts(Question $question, ?Group $group): array + { + $qb = $this->createQueryBuilder('se') + ->join('se.submitter', 'submitter') + ->where('se.question = :question') + ->setParameter('question', $question); + + if ($group) { + $qb->andWhere('submitter.group = :group') + ->setParameter('group', $group); + } else { + $qb->andWhere('submitter.group IS NULL'); + } + + /** + * @var SolutionEvent[] $result + */ + $result = $qb->getQuery()->getResult(); + + return $result; + } } diff --git a/src/Service/PassRateService.php b/src/Service/PassRateService.php new file mode 100644 index 0000000..db696c2 --- /dev/null +++ b/src/Service/PassRateService.php @@ -0,0 +1,36 @@ +solutionEventRepository->getTotalAttempts($question, $group); + + return new PassRate($attempts); + } +} diff --git a/src/Service/Types/PassRate.php b/src/Service/Types/PassRate.php new file mode 100644 index 0000000..067d080 --- /dev/null +++ b/src/Service/Types/PassRate.php @@ -0,0 +1,67 @@ +total = \count($attempts); + $this->passed = \count(array_filter($attempts, fn (SolutionEvent $event) => SolutionEventStatus::Passed == $event->getStatus())); + } + + /** + * Calculate the pass rate of a question. + * + * @return float the pass rate of the question in percentage + */ + public function getPassRate(): float + { + if (0 === $this->total) { + return 0; + } + + return round($this->passed / $this->total * 100, 2); + } + + /** + * @return string the level of the pass rate, can be 'low', 'medium', or 'high' + */ + public function getLevel(): string + { + $passRate = $this->getPassRate(); + + return match (true) { + $passRate <= 40 => 'low', + $passRate <= 70 => 'medium', + default => 'high', + }; + } +} diff --git a/src/Twig/Components/Challenge/Header.php b/src/Twig/Components/Challenge/Header.php index a7f1781..18750b4 100644 --- a/src/Twig/Components/Challenge/Header.php +++ b/src/Twig/Components/Challenge/Header.php @@ -9,6 +9,8 @@ use App\Entity\User; use App\Repository\QuestionRepository; use App\Repository\SolutionEventRepository; +use App\Service\PassRateService; +use App\Service\Types\PassRate; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; #[AsTwigComponent] @@ -21,6 +23,7 @@ final class Header public function __construct( private readonly SolutionEventRepository $solutionEventRepository, private readonly QuestionRepository $questionRepository, + private readonly PassRateService $passRateService, ) { } @@ -42,4 +45,9 @@ public function getPreviousPage(): ?int { return $this->questionRepository->getPreviousPage($this->question->getId()); } + + public function getPassRate(): PassRate + { + return $this->passRateService->getPassRate($this->question, $this->user->getGroup()); + } } diff --git a/src/Twig/Components/Questions/Card.php b/src/Twig/Components/Questions/Card.php index 3effae4..a5b121d 100644 --- a/src/Twig/Components/Questions/Card.php +++ b/src/Twig/Components/Questions/Card.php @@ -6,6 +6,8 @@ use App\Entity\Question; use App\Entity\User; +use App\Service\PassRateService; +use App\Service\Types\PassRate; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; #[AsTwigComponent] @@ -14,21 +16,16 @@ final class Card public Question $question; public User $currentUser; + public function __construct( + private readonly PassRateService $passRateService, + ) { + } + /** - * Get the pass rate level of the question. - * - * Low: 0% - 40% - * Medium: 41 – 70% - * High: 71% - 100% + * Get the pass rate of the question. */ - public function getPassRateLevel(): string + public function getPassRate(): PassRate { - $passRate = $this->question->getPassRate($this->currentUser->getGroup()); - - return match (true) { - $passRate <= 40 => 'low', - $passRate <= 70 => 'medium', - default => 'high', - }; + return $this->passRateService->getPassRate($this->question, $this->currentUser->getGroup()); } } diff --git a/templates/components/Challenge/Header.html.twig b/templates/components/Challenge/Header.html.twig index 7f4d167..eb8a9c8 100644 --- a/templates/components/Challenge/Header.html.twig +++ b/templates/components/Challenge/Header.html.twig @@ -5,10 +5,11 @@
  • {{ question.type }}
  • 通過率 + {% set passRate = this.passRate %} - {{ question.passRate(user.group) }}% + data-bs-title="{{ passRate.total }} 次挑戰中有 {{ passRate.passed }} 次成功"> + {{ passRate.passRate }}%
  • diff --git a/templates/components/Questions/Card.html.twig b/templates/components/Questions/Card.html.twig index 30659a0..35433aa 100644 --- a/templates/components/Questions/Card.html.twig +++ b/templates/components/Questions/Card.html.twig @@ -11,7 +11,8 @@
    進行測驗 -
    通過率 {{ question.passRate(currentUser.group) }}%
    + {% set passRate = this.passRate %} +
    通過率 {{ passRate.passRate }}%