Add bulma css
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
100f7c8ad6
commit
05d77329ab
|
@ -103,7 +103,7 @@ module Backend
|
|||
|
||||
@[GraphQL::Field]
|
||||
# Starts assignment job of mentors to students
|
||||
def assign_students(context : Context) : Bool?
|
||||
def start_assignment(context : Context) : Bool?
|
||||
context.admin!
|
||||
|
||||
Worker::Jobs::AssignStudentsJob.new.enqueue
|
||||
|
|
|
@ -54,115 +54,8 @@ module Backend
|
|||
teachers = Db::Teacher.query
|
||||
.where do
|
||||
raw("EXISTS (SELECT 1 FROM teacher_votes WHERE teacher_id = teachers.id)") &
|
||||
(max_students > 0)
|
||||
max_students > 0
|
||||
end
|
||||
.with_teacher_votes
|
||||
.to_a
|
||||
vote_index = Hash.zip(teachers.map(&.id), [0] * teachers.size)
|
||||
teacher_votes : Hash(Int32, Array(TeacherVote)) = Hash.zip(
|
||||
teachers.map(&.id),
|
||||
teachers.map do |t|
|
||||
t.teacher_votes.map do |tv|
|
||||
vote_index[t.id] += 1
|
||||
|
||||
{
|
||||
student: tv.vote.student.id,
|
||||
priority: tv.priority,
|
||||
}
|
||||
end
|
||||
end
|
||||
)
|
||||
teachers.sort_by! { |t| vote_index[t.id] }
|
||||
|
||||
students = Db::Student.query
|
||||
.with_vote(&.with_teacher_votes(&.order_by(priority: :asc)))
|
||||
.to_a
|
||||
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) }
|
||||
.map do |tv|
|
||||
{
|
||||
teacher: tv.teacher.id,
|
||||
teacher_max_students: tv.teacher.max_students,
|
||||
}
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
best_assignment = {
|
||||
assignment: {} of Int32 => Assignment,
|
||||
score: Float32::INFINITY,
|
||||
}
|
||||
|
||||
Backend.config.assignment_possibility_count.times.each do
|
||||
assignment = {} of Int32 => Assignment
|
||||
assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size)
|
||||
# teachers.each do |t|
|
||||
# queue = Deque.new(teacher_votes[t.id].shuffle)
|
||||
|
||||
# count = 1
|
||||
# while count < t.max_students
|
||||
# break unless x = queue.shift?
|
||||
# tv = x.not_nil!
|
||||
|
||||
# if assignment[tv[:student]]?.nil? || assignment[tv[:student]][:priority] <= tv[:priority]
|
||||
# assignment[tv[:student]] = {teacher: t.id, priority: tv[:priority]}
|
||||
# count += 1
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
votes.to_a.shuffle.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}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pp! assignment, assignment_count
|
||||
|
||||
score = 0_f32
|
||||
# positivity = 0
|
||||
# assignment.each do |s, a|
|
||||
# ratio = (vote_sizes[s] - a[:priority]) / vote_sizes[s]
|
||||
# score += 2 ** ratio
|
||||
# positivity += ratio > 0.5 ? 1 : -1
|
||||
# end
|
||||
assignment.each do |s, a|
|
||||
size = votes[s].size
|
||||
p! a[:priority], (votes[s].size - a[:priority]) / size
|
||||
# score += 1 if ((votes[s].size - a[:priority]) / size) >= 0.5
|
||||
score += a[:priority]
|
||||
end
|
||||
|
||||
# full_score = score ** positivity
|
||||
if score < best_assignment[:score]
|
||||
best_assignment = {
|
||||
assignment: assignment,
|
||||
score: score,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
pp! best_assignment
|
||||
|
||||
str = String.build do |str|
|
||||
str << "===========================\n"
|
||||
best_assignment[:assignment].each do |s, a|
|
||||
str << "#{Db::Student.query.find!(s).user.username} : #{Db::Teacher.query.find!(a[:teacher]).user.username} (#{a[:priority]} / #{votes[s].size})\n"
|
||||
end
|
||||
str << "===========================\n"
|
||||
end
|
||||
print str
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,6 @@ FROM lukemathwalker/cargo-chef:latest-rust-1.65.0 as chef
|
|||
WORKDIR /usr/src/frontend
|
||||
|
||||
FROM chef as planner
|
||||
WORKDIR /usr/src/frontend
|
||||
RUN mkdir src && touch src/main.rs
|
||||
COPY ./Cargo.toml .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
@ -11,10 +10,10 @@ FROM chef as builder
|
|||
WORKDIR /usr/local/bin
|
||||
ARG TRUNK_VERSION="v0.16.0"
|
||||
RUN wget -qO- https://github.com/thedodd/trunk/releases/download/${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
||||
WORKDIR /usr/src/frontend
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
COPY ./.cargo ./.cargo
|
||||
WORKDIR /usr/src/frontend
|
||||
COPY --from=planner /usr/src/frontend/recipe.json .
|
||||
COPY ./.cargo ./.cargo
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
COPY ./index.html .
|
||||
COPY ./graphql ./graphql
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
mutation Login($username: String!, $password: String!) {
|
||||
login(username: $username, password: $password) {
|
||||
token
|
||||
user {
|
||||
admin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
mutation StartAssignment {
|
||||
startAssignment
|
||||
}
|
|
@ -86,7 +86,6 @@ type LoginPayload {
|
|||
}
|
||||
|
||||
type Mutation {
|
||||
assignStudents: Boolean
|
||||
createUser(checkLdap: Boolean! = true, input: UserCreateInput!): User
|
||||
createVote(input: VoteCreateInput!): Vote
|
||||
deleteUser(id: Int!): Int
|
||||
|
@ -94,6 +93,7 @@ type Mutation {
|
|||
logout: UUID
|
||||
registerTeacher(input: TeacherInput!): Teacher
|
||||
revokeToken(token: UUID!): UUID!
|
||||
startAssignment: Boolean
|
||||
}
|
||||
|
||||
input TeacherInput {
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.4/css/bulma.min.css"
|
||||
integrity="sha512-HqxHUkJM0SYcbvxUw5P60SzdOTy/QVwA1JJrvaXJv4q7lmbDZCmZaqz01UPOaQveoxfYRv1tHozWGPMcuTBuvQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -3,6 +3,12 @@ events {
|
|||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
|
||||
server_tokens off;
|
||||
more_clear_headers Server;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use yew_agent::{Bridge, Bridged};
|
|||
use yew_router::prelude::*;
|
||||
|
||||
use crate::agents;
|
||||
use crate::cookie_names;
|
||||
use crate::cookies;
|
||||
use crate::routes;
|
||||
|
||||
pub enum Msg {
|
||||
|
@ -38,7 +38,7 @@ impl Component for LoggedInHandler {
|
|||
Msg::LoggedIn(x) => {
|
||||
if self.logged_in && !x {
|
||||
log::info!("Global logout!");
|
||||
wasm_cookies::delete(cookie_names::TOKEN);
|
||||
cookies::logout_clear();
|
||||
ctx.link().history().unwrap().push(routes::Route::Login);
|
||||
}
|
||||
self.logged_in = x;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
use const_format::concatcp;
|
||||
|
||||
pub const BASE: &str = "mentorenwahl_";
|
||||
pub const TOKEN: &str = concatcp!(BASE, "token");
|
|
@ -0,0 +1,14 @@
|
|||
use const_format::concatcp;
|
||||
|
||||
const BASE: &str = "mentorenwahl_";
|
||||
|
||||
pub const TOKEN: &str = concatcp!(BASE, "token");
|
||||
pub const ADMIN: &str = concatcp!(BASE, "admin");
|
||||
|
||||
pub const DELETE_ON_LOGOUT: [&str; 2] = [TOKEN, ADMIN];
|
||||
|
||||
pub fn logout_clear() {
|
||||
for x in DELETE_ON_LOGOUT {
|
||||
wasm_cookies::delete(x);
|
||||
}
|
||||
}
|
|
@ -2,4 +2,5 @@ pub mod login;
|
|||
pub mod logout;
|
||||
pub mod register_teacher;
|
||||
pub mod revoke_token;
|
||||
pub mod start_assignment;
|
||||
pub mod vote;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use graphql_client::GraphQLQuery;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "graphql/schema.graphql",
|
||||
query_path = "graphql/mutations/start_assignment.graphql",
|
||||
response_derives = "Debug",
|
||||
skip_serializing_none
|
||||
)]
|
||||
pub struct StartAssignment;
|
|
@ -1,6 +1,6 @@
|
|||
pub mod agents;
|
||||
pub mod components;
|
||||
pub mod cookie_names;
|
||||
pub mod cookies;
|
||||
pub mod graphql;
|
||||
pub mod layouts;
|
||||
pub mod routes;
|
||||
|
|
|
@ -33,6 +33,7 @@ impl Component for App {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
|
||||
yew::start_app::<App>();
|
||||
|
|
|
@ -5,7 +5,7 @@ use yew_router::prelude::*;
|
|||
use yew_side_effect::title::Title;
|
||||
|
||||
use crate::components;
|
||||
use crate::cookie_names;
|
||||
use crate::cookies;
|
||||
use crate::graphql;
|
||||
use crate::routes;
|
||||
|
||||
|
@ -50,13 +50,24 @@ impl Component for Login {
|
|||
.unwrap();
|
||||
|
||||
if response.errors.is_some() {
|
||||
wasm_cookies::delete(cookie_names::TOKEN);
|
||||
cookies::logout_clear();
|
||||
} else {
|
||||
let data = response.data.unwrap().login.unwrap();
|
||||
|
||||
wasm_cookies::set(
|
||||
cookie_names::TOKEN,
|
||||
&response.data.unwrap().login.unwrap().token,
|
||||
cookies::TOKEN,
|
||||
&data.token,
|
||||
&wasm_cookies::CookieOptions::default(),
|
||||
)
|
||||
);
|
||||
if data.user.admin {
|
||||
wasm_cookies::set(
|
||||
cookies::ADMIN,
|
||||
"1",
|
||||
&wasm_cookies::CookieOptions::default(),
|
||||
);
|
||||
} else {
|
||||
wasm_cookies::delete(cookies::ADMIN);
|
||||
}
|
||||
}
|
||||
|
||||
Msg::Login(components::graphql_errors::convert(response.errors))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
use crate::cookie_names;
|
||||
use crate::cookies;
|
||||
use crate::layouts;
|
||||
|
||||
pub mod home;
|
||||
|
@ -24,12 +24,12 @@ pub enum Route {
|
|||
|
||||
pub fn switch(routes: &Route) -> Html {
|
||||
let token = {
|
||||
let tmp = wasm_cookies::get(cookie_names::TOKEN);
|
||||
let tmp = wasm_cookies::get(cookies::TOKEN);
|
||||
if let Some(x) = tmp {
|
||||
if let Ok(y) = x {
|
||||
Some(y)
|
||||
} else {
|
||||
wasm_cookies::delete(cookie_names::TOKEN);
|
||||
wasm_cookies::delete(cookies::TOKEN);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
|
@ -37,6 +37,20 @@ pub fn switch(routes: &Route) -> Html {
|
|||
}
|
||||
};
|
||||
let logged_in = token.is_some();
|
||||
let admin = {
|
||||
let tmp = wasm_cookies::get(cookies::ADMIN);
|
||||
if let Some(x) = tmp {
|
||||
if let Ok(_) = x {
|
||||
true
|
||||
} else {
|
||||
wasm_cookies::delete(cookies::ADMIN);
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
log::debug!("admin = {:?}", admin);
|
||||
|
||||
match routes {
|
||||
Route::Home => {
|
||||
|
@ -52,7 +66,7 @@ pub fn switch(routes: &Route) -> Html {
|
|||
html! {
|
||||
<layouts::logged_in::LoggedIn {logged_in}>
|
||||
<layouts::main::Main token={token.to_owned()} {logged_in}>
|
||||
<settings::Settings {token} />
|
||||
<settings::Settings {token} {admin} />
|
||||
</layouts::main::Main>
|
||||
</layouts::logged_in::LoggedIn>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
use graphql_client::reqwest::post_graphql;
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::components;
|
||||
use crate::graphql;
|
||||
|
||||
pub enum Msg {
|
||||
StartAssignment,
|
||||
StartAssignmentDone(Option<Vec<String>>),
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq, Eq)]
|
||||
pub struct AssignmentsProps {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
pub struct Assignments {
|
||||
errors: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Component for Assignments {
|
||||
type Message = Msg;
|
||||
type Properties = AssignmentsProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self { errors: None }
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
Msg::StartAssignment => {
|
||||
let client = graphql::client(Some(&ctx.props().token)).unwrap();
|
||||
ctx.link().send_future(async move {
|
||||
let response =
|
||||
post_graphql::<graphql::mutations::start_assignment::StartAssignment, _>(
|
||||
&client,
|
||||
graphql::URL.as_str(),
|
||||
graphql::mutations::start_assignment::start_assignment::Variables,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Msg::StartAssignmentDone(components::graphql_errors::convert(response.errors))
|
||||
});
|
||||
|
||||
false
|
||||
}
|
||||
Msg::StartAssignmentDone(errors) => {
|
||||
self.errors = errors;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<fieldset>
|
||||
<legend>{ "Zuweisungen" }</legend>
|
||||
<table>
|
||||
<tr>
|
||||
<th>{ "Aktion" }</th>
|
||||
<th>{ "Optionen" }</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{ "Zuweisung starten" }</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>
|
||||
<button onclick={ctx.link().callback(|_| Msg::StartAssignment)}>
|
||||
{ "Ausführen" }
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<components::graphql_errors::GraphQLErrors errors={self.errors.to_owned()} />
|
||||
</fieldset>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
use yew::prelude::*;
|
||||
use yew_side_effect::title::Title;
|
||||
|
||||
pub mod assignments;
|
||||
pub mod tokens;
|
||||
|
||||
#[derive(Properties, PartialEq, Eq)]
|
||||
pub struct SettingsProps {
|
||||
pub token: Option<String>,
|
||||
pub admin: bool,
|
||||
}
|
||||
|
||||
pub struct Settings;
|
||||
|
@ -25,6 +27,11 @@ impl Component for Settings {
|
|||
<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>
|
||||
}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ impl Component for Tokens {
|
|||
}
|
||||
});
|
||||
|
||||
true
|
||||
false
|
||||
}
|
||||
Msg::RevokeDone { errors, id } => {
|
||||
self.errors = errors;
|
||||
|
@ -122,7 +122,7 @@ impl Component for Tokens {
|
|||
}) }
|
||||
</table>
|
||||
|
||||
<components::graphql_errors::GraphQLErrors errors={self.errors.clone()} />
|
||||
<components::graphql_errors::GraphQLErrors errors={self.errors.to_owned()} />
|
||||
}
|
||||
</fieldset>
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue