From 6620dea81263b1d7ac596e9b319ceae68fcc15f1 Mon Sep 17 00:00:00 2001 From: Dominic Grimm Date: Thu, 29 Dec 2022 22:24:29 +0100 Subject: [PATCH] Update --- backend/src/backend/api/schema/mutation.cr | 5 +- backend/src/backend/config.cr | 2 +- backend/src/backend/ldap/user.cr | 2 - .../src/backend/worker/jobs/assignment_job.cr | 79 ++++++++----------- frontend/scss/styles.scss | 1 + frontend/src/layouts/base.rs | 36 +++++++++ frontend/src/layouts/logged_in.rs | 1 - frontend/src/layouts/main.rs | 22 ++---- frontend/src/layouts/mod.rs | 1 + frontend/src/routes/settings/mod.rs | 14 ++-- 10 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 frontend/src/layouts/base.rs diff --git a/backend/src/backend/api/schema/mutation.cr b/backend/src/backend/api/schema/mutation.cr index 20b4f94..a470fc3 100644 --- a/backend/src/backend/api/schema/mutation.cr +++ b/backend/src/backend/api/schema/mutation.cr @@ -84,6 +84,9 @@ module Backend true end user = Db::User.create!(username: input.username, role: input.role.to_db, admin: input.admin) + if input.role.student? + Db::Student.create!(user_id: user.id) + end Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue User.new(user) @@ -105,7 +108,7 @@ module Backend def start_assignment(context : Context) : Bool? context.admin! - Worker::Jobs::AssignStudentsJob.new.enqueue + Worker::Jobs::AssignmentJob.new.enqueue true end diff --git a/backend/src/backend/config.cr b/backend/src/backend/config.cr index 96a9b2b..c26f60b 100644 --- a/backend/src/backend/config.cr +++ b/backend/src/backend/config.cr @@ -62,7 +62,7 @@ module Backend getter minimum_teacher_selection_count : Int32 # Assignment possibility count - getter assignment_possibility_count : Int32 + getter assignment_possibility_count : UInt32 @[EnvConfig::Setting(key: "api")] # Configuration for `Api` diff --git a/backend/src/backend/ldap/user.cr b/backend/src/backend/ldap/user.cr index 94d1221..cfd0fb3 100644 --- a/backend/src/backend/ldap/user.cr +++ b/backend/src/backend/ldap/user.cr @@ -24,11 +24,9 @@ module Backend alias Raw = Hash(String, Array(String)) - @[JSON::Field(key: "givenName")] # First name getter first_name : String - @[JSON::Field(key: "sn")] # Last name getter last_name : String diff --git a/backend/src/backend/worker/jobs/assignment_job.cr b/backend/src/backend/worker/jobs/assignment_job.cr index 1aa4c9a..275b998 100644 --- a/backend/src/backend/worker/jobs/assignment_job.cr +++ b/backend/src/backend/worker/jobs/assignment_job.cr @@ -18,13 +18,9 @@ module Backend module Worker module Jobs # Assigns students to teachers when all students voted - class AssignStudentsJob < Mosquito::QueuedJob + class AssignmentJob < Mosquito::QueuedJob # run_every 1.minute - def rescheduleable? : Bool - false - end - alias TeacherVote = {student: Int32, priority: Int32} alias Assignment = {teacher: Int32, priority: Int32} @@ -33,7 +29,6 @@ module Backend teacher_count = Db::Teacher.query.count student_count = Db::Student.query.count vote_count = Db::Vote.query.count - if teacher_count == 0 log "No teachers found, skipping assignment" fail @@ -58,13 +53,12 @@ module Backend end .with_teacher_votes .to_a - vote_index = Hash.zip(teachers.map(&.id), [0] * teachers.size) + teacher_ids = teachers.map(&.id) + teacher_max_students = Hash.zip(teacher_ids, teachers.map(&.max_students)) teacher_votes : Hash(Int32, Array(TeacherVote)) = Hash.zip( - teachers.map(&.id), + teacher_ids, teachers.map do |t| t.teacher_votes.map do |tv| - vote_index[t.id] += 1 - { student: tv.vote.student.id, priority: tv.priority, @@ -72,78 +66,75 @@ module Backend end end ) - teachers.sort_by! { |t| vote_index[t.id] } students = Db::Student.query .with_vote(&.with_teacher_votes(&.order_by(priority: :desc))) .to_a - students.each do |s| - p! s - pp! s.vote.not_nil!.teacher_votes - end student_ids = students.map(&.id) votes = Hash.zip( student_ids, students.map do |s| s.vote.not_nil!.teacher_votes .to_a - .select { |tv| teacher_votes.has_key?(tv.teacher.id) } + .select! { |tv| teacher_votes.has_key?(tv.teacher.id) } .map do |tv| { - teacher: tv.teacher.id, - teacher_max_students: tv.teacher.max_students, + teacher: tv.teacher.id, + priority: tv.priority, } end end ) votes_a = votes.to_a - best : {assignment: Hash(Int32, Assignment), score: UInt32}? = nil + best : {assignment: Hash(Int32, Assignment), priority_score: UInt32, teacher_score: UInt32}? = nil + empty_assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size) Backend.config.assignment_possibility_count.times.each do assignment = {} of Int32 => Assignment - assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size) + assignment_count = empty_assignment_count.clone votes_a.shuffle(Random::Secure).each do |s, tvs| - tvs.each_with_index do |tv, i| - if assignment[s]?.nil? - assignment_count[tv[:teacher]] += 1 - assignment[s] = {teacher: tv[:teacher], priority: i} - elsif assignment_count[tv[:teacher]] < tv[:teacher_max_students] - assignment_count[assignment[s][:teacher]] -= 1 - assignment_count[tv[:teacher]] += 1 - assignment[s] = {teacher: tv[:teacher], priority: i} + tvs.each do |tv| + if assignment_count[tv[:teacher]] < teacher_max_students[tv[:teacher]] + if assignment[s]?.nil? + assignment_count[tv[:teacher]] += 1 + assignment[s] = {teacher: tv[:teacher], priority: tv[:priority]} + else + assignment_count[assignment[s][:teacher]] -= 1 + assignment_count[tv[:teacher]] += 1 + assignment[s] = {teacher: tv[:teacher], priority: tv[:priority]} + end end end end - score = 0_u32 + priority_score = 0_u32 assignment.each do |_, a| - score += a[:priority] ** 2 + priority_score += a[:priority] ** 2 + end + + teacher_score = 0_u32 + assignment_count.each do |t, c| + teacher_score += (teacher_max_students[t] ** c) * teacher_max_students[t] end if best.nil? best = { - assignment: assignment, - score: score, + assignment: assignment, + priority_score: priority_score, + teacher_score: teacher_score, } - elsif score < best.not_nil![:score] + elsif priority_score < best.not_nil![:priority_score] && teacher_score < best.not_nil![:teacher_score] best = { - assignment: assignment, - score: score, + assignment: assignment, + priority_score: priority_score, + teacher_score: teacher_score, } end end pp! best - # str = String.build do |str| - # str << "===========================\n" - # best.not_nil![:assignment].each do |s, a| - # pp! a - # str << "#{Db::Student.query.find!(s).user.username} #{s} : #{Db::Teacher.query.find!(a[:teacher]).user.username} #{a[:priority]} (#{votes[s].size - a[:priority]} / #{votes[s].size})\n" - # end - # str << "===========================\n" - # end - # print str + Db::Assignment.import(best.not_nil![:assignment].map { |s, a| Db::Assignment.new({student_id: s, teacher_id: a[:teacher]}) }) end end end diff --git a/frontend/scss/styles.scss b/frontend/scss/styles.scss index b7782d3..3998b8c 100644 --- a/frontend/scss/styles.scss +++ b/frontend/scss/styles.scss @@ -1,4 +1,5 @@ body { + // bulma fix overflow-x: hidden; display: flex; diff --git a/frontend/src/layouts/base.rs b/frontend/src/layouts/base.rs new file mode 100644 index 0000000..b08e04b --- /dev/null +++ b/frontend/src/layouts/base.rs @@ -0,0 +1,36 @@ +use yew::prelude::*; + +use crate::components; + +#[derive(Properties, PartialEq)] +pub struct BaseProps { + pub token: Option, + pub logged_in: bool, + #[prop_or_default] + pub children: Children, +} + +pub struct Base; + +impl Component for Base { + type Message = (); + type Properties = BaseProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { + <> + + +
+ + { for ctx.props().children.iter() } +
+ + + } + } +} diff --git a/frontend/src/layouts/logged_in.rs b/frontend/src/layouts/logged_in.rs index e8160bf..7406998 100644 --- a/frontend/src/layouts/logged_in.rs +++ b/frontend/src/layouts/logged_in.rs @@ -46,7 +46,6 @@ impl Component for LoggedIn { fn view(&self, ctx: &Context) -> Html { if !self.logged_in { - log::info!("Viewing logged in required site while not logged in!"); ctx.link().history().unwrap().push(routes::Route::Login); } diff --git a/frontend/src/layouts/main.rs b/frontend/src/layouts/main.rs index e41a88b..169f2b8 100644 --- a/frontend/src/layouts/main.rs +++ b/frontend/src/layouts/main.rs @@ -1,11 +1,7 @@ use yew::prelude::*; -use crate::components; - -pub enum Msg { - LogOutClicked, - LogOut(Option>), -} +// use crate::components; +use crate::layouts; #[derive(Properties, PartialEq)] pub struct MainProps { @@ -18,7 +14,7 @@ pub struct MainProps { pub struct Main; impl Component for Main { - type Message = Msg; + type Message = (); type Properties = MainProps; fn create(_ctx: &Context) -> Self { @@ -27,17 +23,13 @@ impl Component for Main { fn view(&self, ctx: &Context) -> Html { html! { - <> - - -
- -
+ +
+
{ for ctx.props().children.iter() }
- - +
} } } diff --git a/frontend/src/layouts/mod.rs b/frontend/src/layouts/mod.rs index 1f60493..02061f1 100644 --- a/frontend/src/layouts/mod.rs +++ b/frontend/src/layouts/mod.rs @@ -1,2 +1,3 @@ +pub mod base; pub mod logged_in; pub mod main; diff --git a/frontend/src/routes/settings/mod.rs b/frontend/src/routes/settings/mod.rs index 790f43d..e2ed3d9 100644 --- a/frontend/src/routes/settings/mod.rs +++ b/frontend/src/routes/settings/mod.rs @@ -24,14 +24,14 @@ impl Component for Settings { html! { <> - <section> - <tokens::Tokens token={ctx.props().token.as_ref().unwrap().to_owned()} /> - </section> - if ctx.props().admin { - <section> - <assignments::Assignments token={ctx.props().token.as_ref().unwrap().to_owned()} /> + <section> + <tokens::Tokens token={ctx.props().token.as_ref().unwrap().to_owned()} /> </section> - } + if ctx.props().admin { + <section> + <assignments::Assignments token={ctx.props().token.as_ref().unwrap().to_owned()} /> + </section> + } </> } }