Skip to content

Commit

Permalink
fix(teacher_tab): Fix Open/Close handling w.r.t. Assignments
Browse files Browse the repository at this point in the history
* Refactor and fix "Open"/"Closed"/… display code,
  Put function `is_open_or_assigned_globally` in `Learnocaml_data`, returning
  ( GloballyOpen | GloballyOpenOrAssigned
  | GloballyClosedOrAssigned | GloballyClosed )

* Make invariants explicit in OCaml comments

Close #534
Close #558
  • Loading branch information
erikmd committed Aug 25, 2023
1 parent 48583ba commit ac4a718
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 22 deletions.
34 changes: 16 additions & 18 deletions src/app/learnocaml_teacher_tab.ml
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,12 @@ let rec teacher_tab token _select _params () =
H.td [stars_div meta.Exercise.Meta.stars];
H.td [
let cls, text =
match Token.Map.is_empty ES.(st.assignments.token_map),
ES.(st.assignments.default) with
| true, ES.Open -> "exo_open", [%i"Open"]
| true, ES.Closed -> "exo_closed", [%i"Closed"]
| _, (ES.Assigned _ | ES.Closed) ->
"exo_assigned", [%i"Assigned"]
| false, ES.Open -> "exo_assigned", [%i"Open/Assg"]
let open ES in
match is_open_or_assigned_globally st.assignments with
| GloballyOpen -> "exo_open", [%i"Open"]
| GloballyOpenOrAssigned -> "exo_assigned", [%i"Open/Assigned"]
| GloballyClosedOrAssigned -> "exo_assigned", [%i"Assigned"]
| GloballyClosed -> "exo_closed", [%i"Closed"]
in
H.span ~a:[H.a_class [cls]] [H.txt text]
];
Expand Down Expand Up @@ -856,17 +855,12 @@ let rec teacher_tab token _select _params () =
let ids = htbl_keys selected_exercises in
let fstat =
if List.exists (fun id ->
let st = get_status id in
ES.(default_assignment st.assignments = Open))
let st = get_status id in
let open_assg = ES.is_open_or_assigned_globally st.ES.assignments in
open_assg = ES.GloballyOpen || open_assg = ES.GloballyOpenOrAssigned)
ids
then ES.(fun assg ->
match default_assignment assg with
| Open -> set_default_assignment assg Closed
| _ -> assg)
else ES.(fun assg ->
match default_assignment assg with
| Closed -> set_default_assignment assg Open
| _ -> assg)
then ES.set_close_or_assigned_globally
else ES.set_open_or_assigned_globally
in
!exercise_status_change (htbl_keys selected_exercises) fstat;
true)
Expand Down Expand Up @@ -1330,7 +1324,11 @@ let rec teacher_tab token _select _params () =
in
let open_exercises =
SMap.fold (fun ex st acc ->
if ES.(st.assignments.default = Open) then ex::acc else acc)
let open ES in
let global_st = is_open_or_assigned_globally st.assignments in
if global_st = GloballyOpen
|| global_st = GloballyOpenOrAssigned
then ex :: acc else acc)
!status_map []
|> List.rev
in
Expand Down
70 changes: 67 additions & 3 deletions src/state/learnocaml_data.ml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,13 @@ module Exercise = struct
| Closed
| Assigned of {start: float; stop: float}

(** w.r.t. all students as a whole *)
type global_status =
| GloballyOpen (** "Open" *)
| GloballyClosed (** "Closed" *)
| GloballyOpenOrAssigned (** "Open/Assigned" *)
| GloballyClosedOrAssigned (** "Assigned" *)

type assignments = {
token_map: status Token.Map.t;
default: status;
Expand All @@ -513,6 +520,66 @@ module Exercise = struct
| Some a -> a
| None -> a.default

(* Open/Close invariants:
forall exo_status : t,
1.(REQUIRED):
(exo_status.assignments.default <> Open /\ Token.Map.for_all (fun _ st -> st <> Open))
\/ (exo_status.assignments.default <> Closed /\ Token.Map.for_all (fun _ st -> st <> Closed))
2.(IfNormalized):
is_open_assigned_globally exo_status.assignments \in {GloballyOpen, GloballyClosed} ->
exo_status.assignments.token_map = Token.Map.empty
*)
let is_open_or_assigned_globally a =
match a.default with
| Open ->
if Token.Map.exists (fun _tok -> function Assigned _ -> true | _ -> false) a.token_map
then GloballyOpenOrAssigned
else GloballyOpen
| Closed ->
if Token.Map.exists (fun _tok -> function Assigned _ -> true | _ -> false) a.token_map
then GloballyClosedOrAssigned
else GloballyClosed
| Assigned _ ->
if Token.Map.exists (fun _tok -> (=) Open) a.token_map
then GloballyOpenOrAssigned
else GloballyClosedOrAssigned

let make_assignments token_map default =
{ token_map; default }

(** Close assignments status globally (for all unassigned students), namely:
- GloballyOpen -> GloballyClosed
- GloballyOpenOrAssigned -> GloballyClosedOrAssigned
- other -> no-op *)
let set_close_or_assigned_globally a =
match is_open_or_assigned_globally a with
| GloballyOpen -> make_assignments Token.Map.empty Closed
| GloballyOpenOrAssigned ->
make_assignments
(Token.Map.map (function Open -> Closed | st -> st) a.token_map)
(match a.default with Open -> Closed | a -> a)
(* otherwise, maybe: forget the map and re-add all tokens ? *)
| GloballyClosedOrAssigned -> a
| GloballyClosed -> a

(** Open assignments status globally (for all unassigned students), namely:
- GloballyClosed -> GloballyOpen
- GloballyClosedOrAssigned -> GloballyOpenOrAssigned
- other -> no-op
*)
let set_open_or_assigned_globally a =
match is_open_or_assigned_globally a with
| GloballyClosed -> make_assignments Token.Map.empty Open
| GloballyClosedOrAssigned ->
make_assignments
(Token.Map.map (function Closed -> Open | st -> st) a.token_map)
(match a.default with Closed -> Open | a -> a)
(* otherwise, maybe: forget the map and re-add all tokens ? *)
| GloballyOpenOrAssigned -> a
| GloballyOpen -> a

(* Note/Erik: we may also want to implement set_assigned_globally *)

let is_open_assignment token a =
match get_status token a with
| Assigned a ->
Expand Down Expand Up @@ -640,9 +707,6 @@ module Exercise = struct
skills_focus;
assignments = { default; token_map } }

let make_assignments token_map default =
{ token_map; default }

let enc =
let status_enc =
J.union [
Expand Down
33 changes: 33 additions & 0 deletions src/state/learnocaml_data.mli
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,39 @@ module Exercise: sig
val get_status:
Token.t -> assignments -> status

(** Global assignment status, w.r.t. all students as a whole
Invariants: forall exo_status : t,
1.(REQUIRED):
(exo_status.assignments.default <> Open && Token.Map.for_all (fun _ st -> st <> Open))
|| (exo_status.assignments.default <> Closed && Token.Map.for_all (fun _ st -> st <> Closed))
2.(IfNormalized):
is_open_assigned_globally exo_status.assignments \in \{GloballyOpen, GloballyClosed\} ->
exo_status.assignments.token_map = Token.Map.empty
*)
type global_status =
| GloballyOpen (** "Open" *)
| GloballyClosed (** "Closed" *)
| GloballyOpenOrAssigned (** "Open/Assigned" *)
| GloballyClosedOrAssigned (** "Assigned" *)

val is_open_or_assigned_globally: assignments -> global_status

(** Close assignments status globally (for all unassigned students), namely:
- GloballyOpen -> GloballyClosed
- GloballyOpenOrAssigned -> GloballyClosedOrAssigned
- other -> no-op *)
val set_close_or_assigned_globally: assignments -> assignments

(** Open assignments status globally (for all unassigned students), namely:
- GloballyClosed -> GloballyOpen
- GloballyClosedOrAssigned -> GloballyOpenOrAssigned
- other -> no-op
*)
val set_open_or_assigned_globally: assignments -> assignments

val is_open_assignment:
Token.t -> assignments -> [> `Open | `Closed | `Deadline of float]

Expand Down
2 changes: 1 addition & 1 deletion translations/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ msgid "Assigned"
msgstr "Devoir"

#: File "src/app/learnocaml_teacher_tab.ml", line 308, characters 57-68
msgid "Open/Assg"
msgid "Open/Assigned"
msgstr "Ouvert/Devoir"

#: File "src/app/learnocaml_teacher_tab.ml", line 368, characters 49-61 391,
Expand Down

0 comments on commit ac4a718

Please sign in to comment.