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/export/actions.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { replace_all, split } from "@cocalc/util/misc";6import { redux } from "@cocalc/frontend/app-framework";7import { webapp_client } from "@cocalc/frontend/webapp-client";89import { CourseActions } from "../actions";10import { CourseStore } from "../store";1112export class ExportActions {13private course_actions: CourseActions;1415constructor(course_actions: CourseActions) {16this.course_actions = course_actions;17}1819private get_store = (): CourseStore => {20return this.course_actions.get_store();21};2223private path = (ext: string, what: string): string => {24// make path more likely to be python-readable...25const path = this.get_store().get("course_filename");26const p: string = split(replace_all(path, "-", "_")).join("_");27const i: number = p.lastIndexOf(".");28return `course-exports/${p.slice(0, i)}/${what}.${ext}`;29};3031private open_file = (path: string): void => {32const project_id = this.get_store().get("course_project_id");33redux.getProjectActions(project_id).open_file({34path,35foreground: true,36});37};3839private write_file = async (path: string, content: string): Promise<void> => {40const actions = this.course_actions;41const id = actions.set_activity({ desc: `Writing ${path}` });42const project_id = this.get_store().get("course_project_id");43try {44await webapp_client.project_client.write_text_file({45project_id,46path,47content,48});49if (actions.is_closed()) return;50this.open_file(path);51} catch (err) {52if (actions.is_closed()) return;53actions.set_error(`Error writing '${path}' -- '${err}'`);54} finally {55if (actions.is_closed()) return;56actions.set_activity({ id });57}58};5960// newlines and duplicated double-quotes61private sanitize_csv_entry = (s: string): string => {62return s.replace(/\n/g, "\\n").replace(/"/g, '""');63};6465to_csv = async (): Promise<void> => {66const store = this.get_store();67const assignments = store.get_sorted_assignments();68// CSV definition: http://edoceo.com/utilitas/csv-file-format69// i.e. double quotes everywhere (not single!) and double quote in double quotes usually blows up70// We had these nice comments, but actually CSV has no official71// support for comments, and this breaks some parsers, e.g.,72// https://github.com/sagemathinc/cocalc/issues/713873// const timestamp = webapp_client.server_time().toISOString();74// let content = `# Course '${store.getIn(["settings", "title"])}'\n`;75// content += `# exported ${timestamp}\n`;76let content = "Name,Id,Email,";77content +=78(() => {79const result: any[] = [];80for (const assignment of assignments) {81result.push(`\"grade: ${assignment.get("path")}\"`);82}83return result;84})().join(",") + ",";85content +=86(() => {87const result1: any[] = [];88for (const assignment of assignments) {89result1.push(`\"comments: ${assignment.get("path")}\"`);90}91return result1;92})().join(",") + "\n";93for (const student of store.get_sorted_students()) {94var left2;95const grades = (() => {96const result2: any[] = [];97for (const assignment of assignments) {98let grade = store.get_grade(99assignment.get("assignment_id"),100student.get("student_id"),101);102grade = grade != null ? grade : "";103grade = this.sanitize_csv_entry(grade);104result2.push(`\"${grade}\"`);105}106return result2;107})().join(",");108109const comments = (() => {110const result3: any[] = [];111for (const assignment of assignments) {112let comment = store.get_comments(113assignment.get("assignment_id"),114student.get("student_id"),115);116comment = comment != null ? comment : "";117comment = this.sanitize_csv_entry(comment);118result3.push(`\"${comment}\"`);119}120return result3;121})().join(",");122const name = `\"${this.sanitize_csv_entry(123store.get_student_name(student.get("student_id")),124)}\"`;125const email = `\"${126(left2 = store.get_student_email(student.get("student_id"))) != null127? left2128: ""129}\"`;130const id = `\"${student.get("student_id")}\"`;131const line = [name, id, email, grades, comments].join(",");132content += line + "\n";133}134this.write_file(this.path("csv", "grades"), content);135};136137private export_grades = (): object => {138const obj: any = {};139const store = this.get_store();140const assignments = store.get_sorted_assignments();141obj.course = store.getIn(["settings", "title"]);142obj.exported = webapp_client.server_time().toISOString();143obj.assignments = [] as string[];144for (const assignment of assignments) {145obj.assignments.push(assignment.get("path"));146}147const students: any[] = [];148for (const student of store.get_sorted_students()) {149const student_id = student.get("student_id");150const grades: string[] = [];151for (const assignment of assignments) {152const assignment_id = assignment.get("assignment_id");153const grade = store.get_grade(assignment_id, student_id);154grades.push(grade);155}156const comments: string[] = [];157for (const assignment of assignments) {158const assignment_id = assignment.get("assignment_id");159const comment = store.get_comments(assignment_id, student_id);160comments.push(comment);161}162const nbgrader: any[] = [];163for (const assignment of assignments) {164const x =165assignment.getIn(["nbgrader_scores", student_id])?.toJS() ?? {};166for (const path in x) {167for (const id in x[path]) {168const entry = x[path][id];169delete entry.manual;170}171}172nbgrader.push(x);173}174const name = store.get_student_name(student_id);175let email = store.get_student_email(student_id) ?? "None";176const id = student.get("student_id");177students.push({ name, id, email, grades, nbgrader, comments });178}179obj.students = students;180return obj;181};182183to_json = async (): Promise<void> => {184const obj = this.export_grades();185this.write_file(186this.path("json", "grades"),187JSON.stringify(obj, undefined, 2),188);189};190191to_py = async (): Promise<void> => {192const obj = this.export_grades();193let content = "";194for (const key in obj) {195content += `${key} = ${JSON.stringify(obj[key], undefined, 2)}\n`;196}197this.write_file(this.path("py", "grades"), content);198};199200file_use_times = async (assignment_or_handout_id: string): Promise<void> => {201const id = this.course_actions.set_activity({202desc: "Exporting file use times...",203});204try {205const { assignment, handout } = this.course_actions.resolve({206assignment_id: assignment_or_handout_id,207handout_id: assignment_or_handout_id,208});209if (assignment != null) {210const target_json = this.path(211"json",212"file-use-times/assignment/" +213replace_all(assignment.get("path"), "/", "-"),214);215await this.course_actions.assignments.export_file_use_times(216assignment_or_handout_id,217target_json,218);219this.open_file(target_json);220} else if (handout != null) {221const target_json = this.path(222"json",223"file-use-times/handouts/" +224replace_all(handout.get("path"), "/", "-"),225);226await this.course_actions.handouts.export_file_use_times(227assignment_or_handout_id,228target_json,229);230this.open_file(target_json);231} else {232throw Error(233`Unknown handout or assignment "${assignment_or_handout_id}"`,234);235}236} catch (err) {237this.course_actions.set_error(`Error exporting file use times -- ${err}`);238} finally {239this.course_actions.set_activity({ id });240}241};242}243244245