Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixing issue #466: On assignments allow the instructor to set a day/time for it to be make visible #502

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ import {
setReleased,
setTimeLimit,
setVisible,
setHidden,
selectVisible,
selectHidden,
} from "../state/assignment/assignSlice";

import { EditorContainer, EditorChooser } from "./editorModeChooser.jsx";
Expand All @@ -79,7 +82,7 @@ function handleChange() {
if (currentValue.id !== 0 && previousValue && previousValue.id !== 0) {
let changes = diff(previousValue, currentValue)
let keys = Object.keys(changes)
let updateKeys = ["due", "points", "visible", "time_limit", "peer_async_visible", "is_peer", "is_timed", "nopause", "nofeedback", "description"]
let updateKeys = ["due","vis", "hid", "points", "visible", "time_limit", "peer_async_visible", "is_peer", "is_timed", "nopause", "nofeedback", "description"]
let update = keys.filter((k) => updateKeys.includes(k))
if (update.length > 0 && keys.indexOf("id") === -1) {
console.log(`updating assignment ${update}`)
Expand Down Expand Up @@ -114,6 +117,8 @@ function AssignmentEditor() {
const name = useSelector(selectName);
const desc = useSelector(selectDesc);
const due = useSelector(selectDue);
const vis = useSelector(selectVisible)
const hid = useSelector(selectHidden)
const points = useSelector(selectPoints);
const assignData = useSelector(selectAll);
const [items, setItems] = useState(assignData.all_assignments.map((a) => a.name))
Expand Down Expand Up @@ -143,7 +148,8 @@ function AssignmentEditor() {
dispatch(setDue(current.duedate));
dispatch(setPoints(current.points));
dispatch(setId(current.id));
dispatch(setVisible(current.visible));
dispatch(setVisible(current.visibledate));
dispatch(setHidden(current.hindingdate));
dispatch(setIsPeer(current.is_peer));
dispatch(setIsTimed(current.is_timed));
dispatch(setFromSource(current.from_source));
Expand All @@ -167,15 +173,24 @@ function AssignmentEditor() {
name: name,
description: desc,
duedate: due,
visibledate: vis,
hiddingdate: hid,
points: points,
}
dispatch(createAssignment(assignment))
// reset items so create button disappears
setItems(assignData.all_assignments.map((a) => a.name))
}

let placeDate = new Date(due);
const [datetime12h, setDatetime12h] = useState(placeDate);

let placeDate1 = new Date(vis);
const [visibleDate, setVisibleDate] = useState(placeDate1);

let placeDate2 = new Date(hid);
const [hiddenDate, setHiddenDate] = useState(placeDate2);

// We use two representations because the Calendar, internally wants a date
// but Redux gets unhappy with a Date object because its not serializable.
// So we use a string for Redux and a Date object for the Calendar.
Expand All @@ -186,79 +201,110 @@ function AssignmentEditor() {
dispatch(setDue(d));
}

return (
<div className="App">
<div className="p-fluid">
<div className="p-field p-grid">
<label htmlFor="name" >Assignment Name</label>
<AutoComplete
className="field"
id="name"
field="name"
suggestions={items}
completeMethod={search}
placeholder="Enter or select assignment name... start typing"
value={name}
onChange={chooseOrNameAssignment}
dropdown />
{items.length == 0 && name ?
<Button type="button" className="mb-3 md:mb-0" onClick={newAssignment}>Create New</Button>
: null}
<label htmlFor="desc" >
Assignment Description
</label>
<InputText
id="desc"
className="field"
placeholder="Enter assignment description"
value={desc}
onChange={(e) => dispatch(setDesc(e.target.value))}
/>
<div className="contain2col">
<div className="item">
<label htmlFor="due" >
Due
</label>
<Calendar
className="field"
dateFormat="m/d/yy,"
id="due"
value={datetime12h}
placeholder={placeDate.toLocaleString()}
onChange={handleDueChange}
showTime
hourFormat="12"
stepMinute={5}
/>
</div>

<div className="item">
<label htmlFor="points">
Total Points
</label>
<InputNumber
id="points"
disabled
className="field"
placeholder="Points"
value={points}
onChange={(e) => dispatch(setPoints(e.value))}
/>
<div className="field grid">
<label className="col-fixed" htmlFor="visible">Visible to Students</label>
<InputSwitch
id="visible"
className="field"
checked={assignData.visible}
onChange={(e) => dispatch(setVisible(e.value))} />
</div>
</div>
</div>
</div>
const handleVisibleChange = (e) => {
setVisibleDate(e.value);
console.log(`visible change ${visibleDate} ${e.value}`)
let d = e.value.toISOString().replace("Z", "");
dispatch(setVisible(d));
};

const handleHiddenChange = (e) => {
console.log(`hidden change ${hiddenDate} ${e.value}`)
let d = e.value.toISOString().replace("Z", "");
dispatch(setHidden(d));
};

return (
<div className="App">
<div className="p-fluid">
<div className="p-field p-grid">
<label htmlFor="name">Assignment Name</label>
<AutoComplete
className="field"
id="name"
field="name"
suggestions={items}
completeMethod={search}
placeholder="Enter or select assignment name... start typing"
value={name}
onChange={chooseOrNameAssignment}
dropdown
/>
{items.length === 0 && name ? (
<Button type="button" className="mb-3 md:mb-0" onClick={newAssignment}>
Create New
</Button>
) : null}
<label htmlFor="desc">Assignment Description</label>
<InputText
id="desc"
className="field"
placeholder="Enter assignment description"
value={desc}
onChange={(e) => dispatch(setDesc(e.target.value))}
/>
<div className="contain2col">
<div className="item">
<label htmlFor="due">Due</label>
<Calendar
className="field"
dateFormat="m/d/yy"
id="due"
value={datetime12h}
placeholder={placeDate.toLocaleString()}
onChange={handleDueChange}
showTime
hourFormat="12"
stepMinute={5}
/>
</div>
<div className="item">
<label htmlFor="points">Total Points</label>
<InputNumber
id="points"
disabled
className="field"
placeholder="Points"
value={points}
onChange={(e) => dispatch(setPoints(e.value))}
/>
</div>

<div className="item">
<label htmlFor="vis">Assignment Visible on:</label>
<Calendar
className="field"
dateFormat="m/d/yy"
id="vis"
value={visibleDate}
placeholder={placeDate1.toLocaleString()}
onChange={handleVisibleChange}
showTime
hourFormat="12"
stepMinute={5}
/>
</div>

<div className="item">
<label htmlFor="hid">Assignment Hidden on:</label>
<Calendar
className="field"
dateFormat="m/d/yy"
id="hid"
value={hiddenDate}
placeholder={placeDate2.toLocaleString()}
onChange={handleHiddenChange}
showTime
hourFormat="12"
stepMinute={5}
/>
</div>

</div>
</div>
);
</div>
</div>
);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ export const createAssignment = createAsyncThunk(
// make a date that is acceptable on the server
let duedate = new Date(assignData.duedate);
duedate = duedate.toISOString().replace('Z', '');
let visibledate = new Date(assignData.visibledate).toISOString().replace('Z', '');
let hiddingdate = new Date(assignData.hiddingdate).toISOString().replace('Z', '');
let body = {
name: assignData.name,
description: assignData.description,
duedate: duedate,
visibledate: visibledate,
hiddingdate: hiddingdate,
points: assignData.points,
kind: "quickcode",
};
Expand Down Expand Up @@ -303,7 +307,10 @@ let epoch = cDate.getTime();
epoch = epoch + 60 * 60 * 24 * 7 * 1000;
cDate = new Date(epoch);
let defaultDeadline = cDate.toLocaleString();
// old
let visibleEpoch = epoch - 60 * 60 * 24 * 7 * 1000;
let hiddenEpoch = epoch + 60 * 60 * 24 * 7 * 1000;
let visibleDateDefault = new Date(visibleEpoch).toLocaleString();
let hiddingDateDefault = new Date(hiddenEpoch).toLocaleString();

// create a slice for Assignments
// This slice must be registered with the store in store.js
Expand All @@ -319,6 +326,7 @@ let defaultDeadline = cDate.toLocaleString();
* - setId
* - setPoints
* - setVisible
* - setHidden
* - setIsPeer
* - setIsTimed
* - setNoFeedback
Expand Down Expand Up @@ -371,6 +379,7 @@ let defaultDeadline = cDate.toLocaleString();
* @property {Function} reducers.setDesc - Sets the description of the assignment.
* @property {Function} reducers.setDue - Sets the due date of the assignment.
* @property {Function} reducers.setVisible - Sets the visibility of the assignment.
* @property {Function} reducers.setHidden - Sets the visibility of the assignment.
* @property {Function} reducers.setIsPeer - Sets if the assignment is a peer assignment.
* @property {Function} reducers.setIsTimed - Sets if the assignment is timed.
* @property {Function} reducers.setNoFeedback - Sets if the assignment has no feedback.
Expand Down Expand Up @@ -398,7 +407,8 @@ export const assignSlice = createSlice({
desc: "",
duedate: defaultDeadline,
points: 1,
visible: true,
visibledate: visibleDateDefault,
hiddingdate: hiddingDateDefault,
is_peer: false,
is_timed: false,
nofeedback: true,
Expand Down Expand Up @@ -437,8 +447,25 @@ export const assignSlice = createSlice({
state.duedate = action.payload.toISOString().replace('Z', '')
},
setVisible: (state, action) => {
state.visible = action.payload;
// action.payload is a Date object coming from the date picker or a string from the server
// convert it to a string and remove the Z because we don't expect timezone information
if (typeof action.payload === "string") {
state.visibledate = action.payload;
return;
}
state.visibledate = action.payload.toISOString().replace('Z', '')
},

setHidden: (state, action) => {
// action.payload is a Date object coming from the date picker or a string from the server
// convert it to a string and remove the Z because we don't expect timezone information
if (typeof action.payload === "string") {
state.hiddingdate = action.payload;
return;
}
state.hiddingdate = action.payload.toISOString().replace('Z', '')
},

setIsPeer: (state, action) => {
state.is_peer = action.payload;
},
Expand Down Expand Up @@ -596,6 +623,7 @@ export const {
setReleased,
setTimeLimit,
setVisible,
setHidden,
sumPoints,
updateExercise,
updateField,
Expand Down Expand Up @@ -627,5 +655,6 @@ export const selectPoints = (state) => state.assignment.points;
export const selectQuestionCount = (state) => state.assignment.question_count;
export const selectSearchResults = (state) => state.assignment.search_results;
export const selectTimeLimit = (state) => state.assignment.time_limit;
export const selectVisible = (state) => state.assignment.visible;
export const selectVisible = (state) => state.assignment.visibledate;
export const selectHidden = (state) => state.assignment.hiddingdate;
export default assignSlice.reducer;
1 change: 1 addition & 0 deletions bases/rsptx/assignment_server_api/routers/instructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ async def new_assignment(
**request_data.model_dump(),
course=course.id,
visible=True,
hidden=False,
released=False,
from_source=False,
is_peer=False,
Expand Down
14 changes: 8 additions & 6 deletions bases/rsptx/assignment_server_api/routers/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async def get_assignments(
assignments = await fetch_assignments(course.course_name, is_visible=True)
assignments.sort(key=lambda x: x.duedate, reverse=True)
stats_list = await fetch_all_assignment_stats(course.course_name, user.id)
stats = {}
stats = {s.assignment: s for s in stats_list}
for s in stats_list:
stats[s.assignment] = s
rslogger.debug(f"stats: {stats}")
Expand Down Expand Up @@ -206,11 +206,12 @@ async def doAssignment(

return RedirectResponse("/assignment/student/chooseAssignment")

if (
assignment.visible == "F"
or assignment.visible is None
or assignment.visible == False
):
current_time = datetime.datetime.utcnow()

if not (assignment.visibledate <= current_time and

(assignment.hideDate is None or assignment.hideDate >= current_time)):

if await is_instructor(request) is False:
rslogger.error(
f"Attempt to access invisible assignment {assignment_id} by {user.username}"
Expand All @@ -219,6 +220,7 @@ async def doAssignment(

if assignment.points is None:
assignment.points = 0


# This query assumes that questions are on a page and in a subchapter that is
# present in the book. For many questions that is of course a given. But for
Expand Down
Loading