CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/course/nbgrader/nbgrader-button.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Render the nbgrader button at the top of the assignment.
8
*/
9
10
import { Alert, Button, Popconfirm } from "antd";
11
import { redux, useActions, useRedux } from "../../app-framework";
12
import { useMemo, useState } from "react";
13
import { Icon, Gap, Tip } from "../../components";
14
import { CourseStore, NBgraderRunInfo, PARALLEL_DEFAULT } from "../store";
15
import { CourseActions } from "../actions";
16
import { nbgrader_status } from "./util";
17
import { plural } from "@cocalc/util/misc";
18
import { webapp_client } from "@cocalc/frontend/webapp-client";
19
20
interface Props {
21
name: string;
22
assignment_id: string;
23
}
24
25
export function NbgraderButton({ name, assignment_id }: Props) {
26
const actions: undefined | CourseActions = useActions(name);
27
const nbgrader_run_info: NBgraderRunInfo = useRedux([
28
name,
29
"nbgrader_run_info",
30
]);
31
const assignment = useRedux([name, "assignments", assignment_id]);
32
const [show_more_info, set_show_more_info] = useState<boolean>(false);
33
const settings = useRedux([name, "settings"]);
34
35
const status = useMemo(() => {
36
const store: undefined | CourseStore = redux.getStore(name) as any;
37
if (store == null) return;
38
return nbgrader_status(assignment);
39
}, [assignment]); // also depends on all student ids, but not worrying about that for now.
40
41
const running = useMemo(() => {
42
if (nbgrader_run_info == null) return false;
43
const t = nbgrader_run_info.get(assignment_id);
44
if (webapp_client.server_time() - (t ?? 0) <= 1000 * 60 * 10) {
45
// Time starting is set and it's also within the last few minutes.
46
// This "few minutes" is just in case -- we probably shouldn't need
47
// that at all ever, but it could make cocalc state usable in case of
48
// weird issues, I guess). User could also just close and re-open
49
// the course file, which resets this state completely.
50
return true;
51
}
52
return false;
53
}, [nbgrader_run_info]);
54
55
function render_parallel() {
56
const n = settings.get("nbgrader_parallel") ?? PARALLEL_DEFAULT;
57
return (
58
<Tip
59
title={`Nbgrader parallel limit: grade ${n} students at once`}
60
tip="This is the max number of students to grade in parallel. Change this in course configuration."
61
>
62
<div style={{ marginTop: "5px", fontWeight: 400 }}>
63
Grade up to {n} students at once.
64
</div>
65
</Tip>
66
);
67
}
68
69
function render_more_info() {
70
if (status == null) return <span />;
71
const todo = status.not_attempted + status.failed;
72
const total = status.attempted + status.not_attempted;
73
const failed =
74
status.failed > 0 ? ` ${status.failed} failed autograding.` : "";
75
const not_attempted =
76
status.not_attempted > 0
77
? ` ${status.not_attempted} not autograded.`
78
: "";
79
return (
80
<Alert
81
style={{ marginTop: "5px" }}
82
type="success"
83
message={
84
<span style={{ fontSize: "14px" }}>
85
Autograded {status.succeeded}/{total} assignments.{failed}
86
{not_attempted}
87
</span>
88
}
89
description={
90
<div>
91
{todo > 0 && (
92
<span>
93
<br />
94
<Button
95
disabled={running}
96
type={"primary"}
97
onClick={() => {
98
actions?.assignments.run_nbgrader_for_all_students(
99
assignment_id,
100
true,
101
);
102
}}
103
>
104
Autograde {todo} not-graded {plural(todo, "assignment")}
105
</Button>
106
</span>
107
)}
108
{status.attempted > 0 && (
109
<span>
110
<br />
111
<Popconfirm
112
title={`Are you sure you want to autograde ALL ${total} ${plural(
113
total,
114
"student",
115
)}?`}
116
onConfirm={() => {
117
actions?.assignments.run_nbgrader_for_all_students(
118
assignment_id,
119
);
120
}}
121
>
122
<Button
123
danger
124
style={{
125
width: "100%",
126
overflow: "hidden",
127
}}
128
disabled={running}
129
>
130
Autograde all {total} {plural(total, "assignment")}...
131
</Button>
132
</Popconfirm>
133
</span>
134
)}
135
{render_parallel()}
136
{actions && (
137
<SyncGrades
138
actions={actions}
139
assignment_id={assignment_id}
140
running={running}
141
/>
142
)}
143
</div>
144
}
145
/>
146
);
147
}
148
149
const label = running ? (
150
<span>
151
{" "}
152
<Icon name="cocalc-ring" spin />
153
<Gap /> Nbgrader is running
154
</span>
155
) : (
156
<span>Nbgrader...</span>
157
);
158
return (
159
<div style={{ margin: "5px 0" }}>
160
<Button onClick={() => set_show_more_info(!show_more_info)}>
161
<Icon
162
style={{ width: "20px" }}
163
name={show_more_info ? "caret-down" : "caret-right"}
164
/>
165
<Gap /> {label}
166
</Button>
167
{show_more_info && render_more_info()}
168
</div>
169
);
170
}
171
172
interface SyncGradesProps {
173
actions: CourseActions;
174
assignment_id: string;
175
running: boolean;
176
}
177
178
function SyncGrades({ actions, assignment_id, running }: SyncGradesProps) {
179
return (
180
<Popconfirm
181
title={`Copy the nbgrader grades to be the assigned grades for all students, even if there are ungraded manual problems, errors or other issues? You probably don't need to do this.`}
182
onConfirm={() => {
183
actions.assignments.set_nbgrader_scores_for_all_students({
184
assignment_id,
185
force: true,
186
commit: true,
187
});
188
}}
189
overlayStyle={{ maxWidth: "500px" }}
190
>
191
<Button
192
style={{
193
marginTop: "10px",
194
width: "100%",
195
overflow: "hidden",
196
}}
197
disabled={running}
198
>
199
Sync grades...
200
</Button>
201
</Popconfirm>
202
);
203
}
204
205