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/shared-project/actions.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Actions that are specific to the shared project.7*/89import { redux } from "@cocalc/frontend/app-framework";10import { Datastore, EnvVars } from "@cocalc/frontend/projects/actions";11import { CourseActions } from "../actions";12import { CourseStore } from "../store";13import { delay } from "awaiting";1415export class SharedProjectActions {16private actions: CourseActions;1718constructor(actions: CourseActions) {19this.actions = actions;20}2122private get_store = (): CourseStore => {23const store = this.actions.get_store();24if (store == null) throw Error("no store");25return store;26};2728// return the default title and description of the shared project.29private settings = (): {30title: string;31description: string;32image?: string;33} => {34const settings = this.get_store().get("settings");35return {36title: `Shared Project -- ${settings.get("title")}`,37description:38settings.get("description") +39"\n\n---\n\nThis project is shared with all students in the course.",40image: settings.get("custom_image"),41};42};4344set_project_title = (): void => {45const store = this.get_store();46if (store == null) return;47const shared_id = store.get_shared_project_id();48if (!shared_id) return;49const { title } = this.settings();50redux.getActions("projects").set_project_title(shared_id, title);51};5253set_project_description = (): void => {54const store = this.get_store();55if (store == null) return;56const shared_id = store.get_shared_project_id();57if (!shared_id) return;5859const { description } = this.settings();60redux61.getActions("projects")62.set_project_description(shared_id, description);63};6465// start the shared project running, stopping, etc. (if it exists)66action_shared_project = async (action: "start" | "stop"): Promise<void> => {67const store = this.get_store();68if (store == null) {69return;70}71const shared_project_id = store.get_shared_project_id();72if (!shared_project_id) {73return; // no shared project74}75const a = redux.getActions("projects");76if (a == null) return;77const f = a[action + "_project"].bind(a);78if (f == null) return;79await f(shared_project_id);80};8182// configure the shared project so that it has everybody as collaborators83configure = async (): Promise<void> => {84const store = this.get_store();85const shared_project_id = store.get_shared_project_id();86if (!shared_project_id) {87return; // no shared project88}89const id = this.actions.set_activity({90desc: "Configuring shared project...",91});92try {93await this.set_project_title();94// add collabs -- all collaborators on course project and all students95const projects = redux.getStore("projects");96const shared_project_users = projects.get_users(shared_project_id);97if (shared_project_users == null) {98return;99}100const course_project_users = projects.get_users(101store.get("course_project_id"),102);103if (course_project_users == null) {104return;105}106const student_account_ids = {};107store.get_students().map((student, _) => {108if (!student.get("deleted")) {109const account_id = student.get("account_id");110if (account_id != null) {111student_account_ids[account_id] = true;112}113}114});115116// Each of shared_project_users or course_project_users are117// immutable.js maps from account_id's to something, and students is a map from118// the student account_id's.119// Our goal is to ensure that:120// {shared_project_users} = {course_project_users} union {students}.121122const actions = redux.getActions("projects");123if (!store.get_allow_collabs()) {124// Ensure the shared project users are all either course or students125for (const account_id in shared_project_users.toJS()) {126if (127!course_project_users.get(account_id) &&128!student_account_ids[account_id]129) {130await actions.remove_collaborator(shared_project_id, account_id);131}132}133}134// Ensure every course project user is on the shared project135for (const account_id in course_project_users.toJS()) {136if (!shared_project_users.get(account_id)) {137await actions.invite_collaborator(shared_project_id, account_id);138}139}140// Ensure every student is on the shared project141for (const account_id in student_account_ids) {142if (!shared_project_users.get(account_id)) {143await actions.invite_collaborator(shared_project_id, account_id);144}145}146147// Set license key(s) on the shared project too, if there is one148// NOTE: we never remove it or any other licenses from the shared project,149// since instructor may want to augment license with another.150const site_license_id = store.getIn(["settings", "site_license_id"]);151if (site_license_id) {152try {153await actions.add_site_license_to_project(154shared_project_id,155site_license_id,156);157} catch (err) {158console.warn(`error adding site license to shared project -- ${err}`);159}160}161162// Also set the compute image163await this.set_project_compute_image();164await this.set_datastore_and_envvars();165} catch (err) {166this.actions.set_error(`Error configuring shared project - ${err}`);167} finally {168this.actions.set_activity({ id });169}170};171172set_project_compute_image = async (): Promise<void> => {173const store = this.get_store();174const shared_project_id = store.get_shared_project_id();175if (!shared_project_id) {176return; // no shared project177}178const dflt_img = await redux.getStore("customize").getDefaultComputeImage();179const img_id = store.get("settings").get("custom_image") ?? dflt_img;180const actions = redux.getProjectActions(shared_project_id);181await actions.set_compute_image(img_id);182};183184set_datastore_and_envvars = async (): Promise<void> => {185const store = this.get_store();186const shared_project_id = store.get_shared_project_id();187if (!shared_project_id) {188return; // no shared project189}190const datastore: Datastore = store.get_datastore();191const envvars: EnvVars = store.get_envvars();192const actions = redux.getActions("projects");193await actions.set_project_course_info({194project_id: shared_project_id,195course_project_id: store.get("course_project_id"),196path: store.get("course_filename"),197pay: "", // pay198payInfo: null, // payInfo199account_id: null, // account_id200email_address: null, // email_address201datastore,202type: "shared", // type of project203student_project_functionality: null, // student_project_functionality (not used for shared projects)204envvars,205});206};207208// set the shared project id in our syncdb209private set_project_id = (shared_project_id: string): void => {210this.actions.set({211table: "settings",212shared_project_id,213});214};215216// create the globally shared project if it doesn't exist217create = async (): Promise<void> => {218const store = this.get_store();219if (store.get_shared_project_id()) {220return;221}222const id = this.actions.set_activity({223desc: "Creating shared project...",224});225let project_id: string;226try {227project_id = await redux228.getActions("projects")229.create_project(this.settings());230} catch (err) {231this.actions.set_error(`error creating shared project -- ${err}`);232return;233} finally {234this.actions.set_activity({ id });235}236this.set_project_id(project_id);237// wait for any changes to syncdb to update store, before238// calling configure (which relies on the store being updated).239await delay(10);240await this.configure();241};242243// Delete the shared project, removing students too.244delete = async (): Promise<void> => {245const store = this.get_store();246const shared_id = store.get_shared_project_id();247if (!shared_id) {248return;249}250const project_actions = redux.getActions("projects");251// delete project252await project_actions.delete_project(shared_id);253254// remove student collabs255const ids = store.get_student_ids({ deleted: false });256if (ids == undefined) {257return;258}259for (const student_id of ids) {260const student_account_id = store.unsafe_getIn([261"students",262student_id,263"account_id",264]);265if (student_account_id) {266await project_actions.remove_collaborator(267shared_id,268student_account_id,269);270}271}272// make the course itself forget about the shared project:273this.actions.set({274table: "settings",275shared_project_id: "",276});277};278}279280281