Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/course/nbgrader/scores.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Component that shows all the scores for all problems and notebooks in a given assignment.7*/89import { Alert, Card } from "antd";10import { Icon } from "@cocalc/frontend/components";11import { useActions } from "@cocalc/frontend/app-framework";12import { useState } from "react";13import {14NotebookScores,15Score,16} from "@cocalc/frontend/jupyter/nbgrader/autograde";17import { get_nbgrader_score } from "../store";18import { CourseActions } from "../actions";19import { autograded_filename } from "../util";2021interface Props {22nbgrader_scores: { [ipynb: string]: NotebookScores | string };23nbgrader_score_ids?: { [ipynb: string]: string[] };24assignment_id: string;25student_id: string;26name: string;27show_all?: boolean;28set_show_all?: () => void;29}3031interface State {32filename?: string;33id?: string;34}3536export function NbgraderScores({37nbgrader_scores,38nbgrader_score_ids,39assignment_id,40student_id,41name,42show_all,43set_show_all,44}: Props) {45const actions = useActions<CourseActions>({ name });4647const [editingScore, setEditingScore] = useState<State>({});4849function render_show_all() {50if (!show_all) return;51const v: JSX.Element[] = [];52for (const filename in nbgrader_scores) {53v.push(render_info_for_file(filename, nbgrader_scores[filename]));54}55return <div>{v}</div>;56}5758function render_info_for_file(59filename: string,60scores: NotebookScores | string,61) {62return (63<div key={filename} style={{ marginBottom: "5px" }}>64{render_filename_links(filename)}65{render_scores_for_file(filename, scores)}66</div>67);68}6970function open_filename(filename: string): void {71actions.assignments.open_file_in_collected_assignment(72assignment_id,73student_id,74filename,75);76}7778function render_filename_links(filename: string) {79const filename2 = autograded_filename(filename);80return (81<div style={{ fontSize: "12px" }}>82<a83style={{ fontFamily: "monospace" }}84onClick={() => open_filename(filename)}85>86{filename}87</a>88<br />89<a90style={{ fontFamily: "monospace" }}91onClick={() => open_filename(filename2)}92>93{filename2}94</a>95</div>96);97}9899function render_scores_for_file(100filename: string,101scores: NotebookScores | string,102) {103if (typeof scores == "string") {104return (105<Alert106type="error"107message={scores + "\n- try running nbgrader again."}108/>109);110}111const v: JSX.Element[] = [];112113const ids: string[] = nbgrader_score_ids?.[filename] ?? [];114for (const id in scores) {115if (!ids.includes(id)) {116ids.push(id);117}118}119120for (const id of ids) {121if (scores[id] != null) {122v.push(render_score(filename, id, scores[id]));123}124}125126const style = { padding: "5px" };127return (128<table129style={{130border: "1px solid lightgray",131width: "100%",132borderRadius: "3px",133borderCollapse: "collapse",134}}135>136<thead>137<tr key={"header"} style={{ border: "1px solid grey" }}>138<th style={style}>Problem</th>139<th style={style}>Score</th>140</tr>141</thead>142<tbody>{v}</tbody>143</table>144);145}146147function set_score(filename: string, id: string, value: string): void {148const score = parseFloat(value);149if (isNaN(score) || !isFinite(score)) {150return; // invalid scores gets thrown away151}152actions.assignments.set_specific_nbgrader_score(153assignment_id,154student_id,155filename,156id,157score,158true,159);160}161162function render_assigned_score(filename: string, id: string, score: Score) {163if (!score.manual) {164return <>{score.score ?? "?"}</>;165}166167const value = `${score.score != null ? score.score : ""}`;168const style = {169width: "48px",170color: "#666",171fontSize: "14px",172border: "1px solid lightgrey",173display: "inline-block",174padding: "1px",175};176if (editingScore.filename == filename && editingScore.id == id) {177return (178<input179spellCheck={false}180autoFocus181type="input"182defaultValue={value}183onBlur={(e) => stop_editing_score((e.target as any).value)}184style={style}185/>186);187} else {188return (189<span style={style} onClick={() => setEditingScore({ filename, id })}>190{value ? value : "-"}191</span>192);193}194}195196function stop_editing_score(value: string): void {197if (editingScore.id != null && editingScore.filename != null) {198set_score(editingScore.filename, editingScore.id, value);199}200setEditingScore({201filename: undefined,202id: undefined,203});204}205206function render_score(filename: string, id: string, score: Score) {207const backgroundColor = score.score == null ? "#fff1f0" : undefined;208const style = { padding: "5px", backgroundColor };209return (210<tr key={id}>211<td style={style}>{id}</td>212<td style={style}>213{render_assigned_score(filename, id, score)} / {score.points}214{render_needs_score(score)}215</td>216</tr>217);218}219220function render_needs_score(score: Score) {221if (!score.manual || score.score != null) return;222return (223<div>224<Icon name="exclamation-triangle" /> Enter score above225</div>226);227}228229function render_more_toggle(action_required: boolean) {230return (231<a onClick={() => set_show_all?.()}>232{action_required ? (233<>234<Icon name="exclamation-triangle" />{" "}235</>236) : undefined}237{show_all ? "" : "Edit..."}238</a>239);240}241242function render_title(score, points, error) {243return (244<span>245<b>nbgrader:</b> {error ? "error" : `${score}/${points}`}246</span>247);248}249250const { score, points, error, manual_needed } =251get_nbgrader_score(nbgrader_scores);252253const action_required: boolean = !!(!show_all && (manual_needed || error));254255const backgroundColor = action_required ? "#fff1f0" : undefined;256257return (258<Card259size="small"260style={{ marginTop: "5px", backgroundColor }}261extra={render_more_toggle(action_required)}262title={render_title(score, points, error)}263styles={{ body: show_all ? {} : { padding: 0 } }}264>265{render_show_all()}266</Card>267);268}269270271