diff --git a/.env.example b/.env.example index 1d039195..e4dd532c 100644 --- a/.env.example +++ b/.env.example @@ -57,4 +57,6 @@ VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" OCTANE_HTTPS=false OCTANE_WORKERS=4 -OCTANE_MAX_REQUESTS=512 \ No newline at end of file +OCTANE_MAX_REQUESTS=512 + +DIVISION_DEBUG_LOG= diff --git a/app/Helpers/DivisionLogger.php b/app/Helpers/DivisionLogger.php new file mode 100644 index 00000000..735acaf4 --- /dev/null +++ b/app/Helpers/DivisionLogger.php @@ -0,0 +1,102 @@ +logFilePath = $logFilePath; + $this->writeFlags = $writeFlags; + } + + /** + * Logs current state of specified event + * + * @param Event $event The event that will be logged + * @return void + */ + public function logEvent(Event $event) + { + $groupsTotal = $event->groups()->count(); + $regsTotal = $event->registrations()->count(); + $nonDrinkerRegsTotal = $event->registrations()->where('drinks_alcohol', '=', false)->count(); + + $line = sprintf('Event %d :: %d groups, %d total regs, %d non-drinker regs (%d %%)', $event->id, $groupsTotal, $regsTotal, $nonDrinkerRegsTotal, $nonDrinkerRegsTotal/$regsTotal * 100); + + $line .= "\n"; + foreach (Course::all() as $course) { + $regs = $event->registrations()->get(); + $regsOfCourse = $regs->toQuery() + ->join('users', 'registrations.user_id', '=', 'users.id') + ->where('users.course_id', '=', $course->id) + ->get(); + $regsCount = $regsOfCourse->count(); + $nonDrinkerRegsCount = $regsOfCourse->where('drinks_alcohol', '=', false)->count(); + + $line .= sprintf('%s : [ %d t (%d %%), %d nd ] ;', $course->abbreviation, $regsCount, $regsCount/$regsTotal * 100, $nonDrinkerRegsCount); + } + + $this->logMsg($line . "\n-----\n"); + + foreach ($event->groups as $group) { + $this->logGroup($group); + } + + $this->logMsg("-----\n"); + } + + /** + * Logs current state of specified group + * + * @param Group $group The group that will be logged + * @return void + */ + public function logGroup(Group $group) + { + $regsTotal = $group->registrations()->count(); + $nonDrinkerRegsTotal = $group->registrations()->where('drinks_alcohol', '=', false)->count(); + + $line = sprintf('Group %d :: %d total, %d non-drinker -- ', $group->id, $regsTotal, $nonDrinkerRegsTotal); + + if ($regsTotal == 0) { + $line .= "\n"; + $this->logMsg($line); + return; + } + + $this->logMsg($line); + + $line = ""; + foreach (Course::all() as $course) { + $regs = $group->registrations()->get(); + $regsOfCourse = $regs->toQuery() + ->join('users', 'registrations.user_id', '=', 'users.id') + ->where('users.course_id', '=', $course->id) + ->get(); + $regsCount = $regsOfCourse->count(); + $nonDrinkerRegsCount = $regsOfCourse->where('drinks_alcohol', '=', false)->count(); + + $line .= sprintf('%s : [ %d t (%d %%), %d nd ] ;', $course->abbreviation, $regsCount, $regsCount/$regsTotal * 100, $nonDrinkerRegsCount); + } + $this->logMsg($line . "\n"); + } + + /** + * Logs a single message + * + * @param string $msg + * @return void + */ + public function logMsg(string $msg) + { + file_put_contents($this->logFilePath, $msg, $this->writeFlags); + } +} diff --git a/app/Helpers/GroupBalancedDivision.php b/app/Helpers/GroupBalancedDivision.php index 63c6023b..707460de 100644 --- a/app/Helpers/GroupBalancedDivision.php +++ b/app/Helpers/GroupBalancedDivision.php @@ -156,51 +156,6 @@ protected function assignUntilSatisfies() $this->assignBalanced($unassignedRegs, $this->registrations); } - // TODO If confident that this is not necessary anymore, just delete - protected function assignLeftoverTo(Collection $leftoverRegs, Collection $groups) - { - // Sort groups by how many registrations are assigned to it - $sortedGroups = $groups->sortBy(function ($group) { - return $group->registrations()->count(); - }); - - // Assign registrations to the groups - $i = 0; - $amountOfGroups = $sortedGroups->count(); - foreach ($leftoverRegs as $registration) { - if ($i >= $amountOfGroups) { - $i = 0; - } - - $registration->group_id = $sortedGroups[$i]->id; - $registration->save(); - $i++; - } - } - - // TODO If confident that this is not necessary anymore, just delete - public function assignLeftoverAlt() - { - // Get only registrations that have yet to be assigned a group - $unassignedRegs = $this->getUnassignedRegs(); - - if ($this->assignByAlc) { - $unassignedNonDrinkers = $unassignedRegs->where('drinks_alcohol', '=', false); - $groupsWithNonDrinkers = $this->groups->filter(function ($val, $key) { - return $val->registrations() - ->where('drinks_alcohol', '=', false) - ->count() > 0; - }); - - $this->assignLeftoverTo($unassignedNonDrinkers, $groupsWithNonDrinkers); - } - - // Refresh in case non-drinkers got assigned - $unassignedRegs = $unassignedRegs->where('group_id', '=', null); - - $this->assignLeftoverTo($unassignedRegs, $this->groups); - } - /** * {@inheritDoc} */ @@ -212,11 +167,15 @@ public function assign() }); if ($groupsNotAssigned) { + $this->loggingEnabled ? $this->logCurrState("Pre-assignInitial") : 0; $this->assignInitial(); + $this->loggingEnabled ? $this->logCurrState("Post-assignInitial") : 0; } + $this->loggingEnabled ? $this->logCurrState("Pre-assignLeftover") : 0; $this->assignLeftover(); if ($this->maxGroupSize > 0) { $this->updateQueuePos($this->getUnassignedRegs()->sortBy('queue_position')); } + $this->loggingEnabled ? $this->logCurrState("Post-assignLeftover") : 0; } } diff --git a/app/Helpers/GroupCourseDivision.php b/app/Helpers/GroupCourseDivision.php index 475c38ef..c146bafa 100644 --- a/app/Helpers/GroupCourseDivision.php +++ b/app/Helpers/GroupCourseDivision.php @@ -98,11 +98,15 @@ public function assign() }); if ($courseNotAssigned) { + $this->loggingEnabled ? $this->logCurrState("Pre-assignInitial") : 0; $this->assignInitial(); + $this->loggingEnabled ? $this->logCurrState("Post-assignInitial") : 0; } + $this->loggingEnabled ? $this->logCurrState("Pre-assignLeftover") : 0; $this->assignLeftover(); if ($this->maxGroupSize > 0) { $this->updateQueuePos($this->getUnassignedRegs()->sortBy('queue_position')); } + $this->loggingEnabled ? $this->logCurrState("Pre-assignLeftover") : 0; } } diff --git a/app/Helpers/GroupDivision.php b/app/Helpers/GroupDivision.php index 14ed72a4..24faaea5 100644 --- a/app/Helpers/GroupDivision.php +++ b/app/Helpers/GroupDivision.php @@ -22,6 +22,9 @@ abstract class GroupDivision protected int $minNonDrinkers; + protected bool $loggingEnabled; + protected string $loggingFilePath; + public function __construct(Event $event, bool $assignByAlc, int $maxGroups = 0, int $maxGroupSize = 0, int $minNonDrinkers = 3) { $this->event = $event; @@ -31,6 +34,37 @@ public function __construct(Event $event, bool $assignByAlc, int $maxGroups = 0, $this->maxGroups = $maxGroups; $this->maxGroupSize = $maxGroupSize; $this->minNonDrinkers = $minNonDrinkers; + + $this->loggingEnabled = false; + $this->loggingFilePath = storage_path('logs/' . env('DIVISION_DEBUG_LOG', 'division.log')); + } + + /** + * Enables detailed logging for this division to a specified file path. Use before calling "assign()" + * + * @param string $logFilePath Path where the logs will be written + * @return void + */ + public function enableLogging(string $logFilePath = "") + { + $this->loggingEnabled = true; + if ($logFilePath) $this->loggingFilePath = $logFilePath; + } + + /** + * Logs current state of this division (meaning groups with info on their assigned registrations) if logging is enabled + * + * @param string $statename Describes what state the division is in at the time of logging + * @return void + */ + public function logCurrState(string $statename) + { + if ($this->loggingEnabled == false) return; + + $logger = new DivisionLogger($this->loggingFilePath); + + $logger->logMsg("-----\n DIVISION STATE: " . $statename . "\n-----\n"); + $logger->logEvent($this->event); } /** @@ -100,8 +134,10 @@ protected function assignInitial() { if ($this->assignByAlc) { $this->assignNonDrinkers(); + $this->loggingEnabled ? $this->logCurrState("Post-assignNonDrinkers") : 0; } $this->assignUntilSatisfies(); + $this->loggingEnabled ? $this->logCurrState("Post-assignUntilSatisfies") : 0; if ($this->maxGroupSize > 0) { $this->updateQueuePos($this->getUnassignedRegs()); } @@ -144,10 +180,16 @@ public function assignLeftover() $cycleAssignByAlc = $this->assignByAlc; // Determines if this assign cycle should consider alcohol consumption at any given time - foreach ($unassignedRegs as $registration) { + while ($unassignedRegs->isNotEmpty()) { $group = $groupsWithOpenSpots->first(); - if ($cycleAssignByAlc) { + // If there are no non-drinkers left, the following assign cycles can safely skip alcohol considerations + if ($cycleAssignByAlc && $unassignedRegs->where('drinks_alcohol', '=', false)->count() == 0) + $cycleAssignByAlc = false; + + $registration = $unassignedRegs->pop(); + + if ($cycleAssignByAlc && $registration->drinks_alcohol) { $group = $groupsWithOpenSpots->filter(function ($val, $key) { return $val->registrations() ->where('drinks_alcohol', '=', false) diff --git a/app/Http/Controllers/DivisionLoggingController.php b/app/Http/Controllers/DivisionLoggingController.php new file mode 100644 index 00000000..9faa3cc8 --- /dev/null +++ b/app/Http/Controllers/DivisionLoggingController.php @@ -0,0 +1,39 @@ +logGroup($group); + + return sprintf('DIVISION_LOGGER: Wrote group log (%s)', storage_path($logPath)); + } + + public function logEvt(int $eventId) + { + $logPath = 'logs/' . env('DIVISION_DEBUG_LOG', 'division.log'); + $logger = new DivisionLogger(storage_path($logPath)); + + $event = Event::find($eventId); + + if (! $event) return sprintf('DIVISION_LOGGER_ERROR: Could not find event with ID %&d', $eventId); + + $logger->logEvent($event); + + return sprintf('DIVISION_LOGGER: Wrote event log (%s)', storage_path($logPath)); + } +} diff --git a/config/database.php b/config/database.php index 86bce1b3..ff91b039 100644 --- a/config/database.php +++ b/config/database.php @@ -108,6 +108,25 @@ 'migrations' => 'migrations', + /* + |-------------------------------------------------------------------------- + | Testing Seeder Values + |-------------------------------------------------------------------------- + | + | These values can be used to dynamically configure the testing seeder. + | For example, we can decide for what course and how many registrations + | (drinker and non-drinker) the seeder should create. + | + */ + + 'testingSeeder' => [ + 'event_id' => env('SEEDER_EVENT_ID', 1), + 'course_id' => env('SEEDER_COURSE_ID', 1), + 'slot_id' => env('SEEDER_SLOT_ID', null), + 'regs_total' => env('SEEDER_TOTAL', 1), + 'regs_nd' => env('SEEDER_NONDRINKERS', 1) + ], + /* |-------------------------------------------------------------------------- | Redis Databases diff --git a/database/seeders/TestingSeeder.php b/database/seeders/TestingSeeder.php new file mode 100644 index 00000000..68bb70ee --- /dev/null +++ b/database/seeders/TestingSeeder.php @@ -0,0 +1,62 @@ +has( + Registration::factory() + ->state([ + 'event_id' => (int) $event_id, + 'slot_id' => $slot_id, + 'group_id' => null, + 'drinks_alcohol' => true, + 'fulfils_requirements' => false, + 'is_present' => false, + 'queue_position' => -1, + ]) + ->count(1) + ) + ->state(['course_id' => (int) $course_id]) + ->count($amount) + ->create(); + + // Non-Drinkers + $amount = (int) $regs_nd; + User::factory() + ->has( + Registration::factory() + ->state([ + 'event_id' => (int) $event_id, + 'slot_id' => $slot_id, + 'group_id' => null, + 'drinks_alcohol' => false, + 'fulfils_requirements' => false, + 'is_present' => false, + 'queue_position' => -1, + ]) + ->count(1) + ) + ->state(['course_id' => (int) $course_id]) + ->count($amount) + ->create(); + } +} diff --git a/routes/web.php b/routes/web.php index 3f85ebb5..b796b4a5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,6 +8,7 @@ use App\Http\Controllers\DashboardEventController; use App\Http\Controllers\DashboardTutorController; use App\Http\Controllers\DatabaseTestController; +use App\Http\Controllers\DivisionLoggingController; use App\Http\Middleware\ActiveModule; use App\Http\Middleware\Authenticate; use App\Http\Middleware\IsLoggedInAdmin;