Skip to content

Commit 4c660c9

Browse files
committed
course: student project restriction copying
1 parent 5b69756 commit 4c660c9

File tree

2 files changed

+90
-30
lines changed

2 files changed

+90
-30
lines changed

src/packages/frontend/course/configuration/actions.ts

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,19 @@ import { store as projects_store } from "@cocalc/frontend/projects/store";
1717
import { webapp_client } from "@cocalc/frontend/webapp-client";
1818
import { reuseInFlight } from "@cocalc/util/reuse-in-flight";
1919
import { CourseActions, primary_key } from "../actions";
20-
import { DEFAULT_LICENSE_UPGRADE_HOST_PROJECT } from "../store";
20+
import {
21+
DEFAULT_LICENSE_UPGRADE_HOST_PROJECT,
22+
CourseSettingsRecord,
23+
PARALLEL_DEFAULT,
24+
} from "../store";
2125
import { SiteLicenseStrategy, SyncDBRecord, UpgradeGoal } from "../types";
22-
import { StudentProjectFunctionality } from "./customize-student-project-functionality";
26+
import {
27+
StudentProjectFunctionality,
28+
completeStudentProjectFunctionality,
29+
} from "./customize-student-project-functionality";
2330
import type { PurchaseInfo } from "@cocalc/util/licenses/purchase/types";
2431
import { delay } from "awaiting";
2532

26-
export const CONFIGURATION_GROUPS = [
27-
"collaborator-policy",
28-
"email-invitation",
29-
"copy-limit",
30-
"restrict-student-projects",
31-
"nbgrader",
32-
"network-file-systems",
33-
"env-variables",
34-
"upgrades",
35-
"software-environment",
36-
] as const;
37-
38-
export type ConfigurationGroup = (typeof CONFIGURATION_GROUPS)[number];
39-
4033
interface ConfigurationTarget {
4134
project_id: string;
4235
path: string;
@@ -130,6 +123,10 @@ export class ConfigurationActions {
130123
set_student_project_functionality = async (
131124
student_project_functionality: StudentProjectFunctionality,
132125
): Promise<void> => {
126+
console.log(
127+
"set_student_project_functionalit",
128+
student_project_functionality,
129+
);
133130
this.set({ student_project_functionality, table: "settings" });
134131
await this.course_actions.student_projects.configure_all_projects();
135132
};
@@ -234,7 +231,7 @@ export class ConfigurationActions {
234231
}
235232
};
236233

237-
set_copy_parallel = (copy_parallel: number): void => {
234+
set_copy_parallel = (copy_parallel: number = PARALLEL_DEFAULT): void => {
238235
this.set({
239236
copy_parallel,
240237
table: "settings",
@@ -437,7 +434,6 @@ export class ConfigurationActions {
437434
groups: ConfigurationGroup[];
438435
targets: ConfigurationTarget[];
439436
}) => {
440-
console.log("copyConfiguration", { groups, targets });
441437
const store = this.course_actions.get_store();
442438
if (groups.length == 0 || targets.length == 0 || store == null) {
443439
return;
@@ -449,17 +445,11 @@ export class ConfigurationActions {
449445
maxTimeMs: 30000,
450446
});
451447
for (const group of groups) {
452-
console.log("copyConfiguration: copy ", target, {
453-
allow_collabs: !!settings.get("allow_collabs"),
454-
table: "settings",
448+
await configureGroup({
449+
group,
450+
settings,
451+
actions: targetActions.course_actions,
455452
});
456-
if (group == "collaborator-policy") {
457-
const allow_colabs = !!settings.get("allow_collabs");
458-
targetActions.course_actions.configuration.set_allow_collabs(
459-
allow_colabs,
460-
);
461-
}
462-
targetActions.course_actions.syncdb.save();
463453
}
464454
}
465455
// switch back
@@ -484,3 +474,50 @@ async function openCourseFileAndGetActions({ project_id, path, maxTimeMs }) {
484474
}
485475
throw Error(`unable to open '${path}'`);
486476
}
477+
478+
export const CONFIGURATION_GROUPS = [
479+
"collaborator-policy",
480+
"email-invitation",
481+
"copy-limit",
482+
"restrict-student-projects",
483+
"nbgrader",
484+
"network-file-systems",
485+
"env-variables",
486+
"upgrades",
487+
"software-environment",
488+
] as const;
489+
490+
export type ConfigurationGroup = (typeof CONFIGURATION_GROUPS)[number];
491+
492+
async function configureGroup({
493+
group,
494+
settings,
495+
actions,
496+
}: {
497+
group: ConfigurationGroup;
498+
settings: CourseSettingsRecord;
499+
actions: CourseActions;
500+
}) {
501+
console.log("configureGroup:", group);
502+
switch (group) {
503+
case "collaborator-policy":
504+
const allow_colabs = !!settings.get("allow_collabs");
505+
actions.configuration.set_allow_collabs(allow_colabs);
506+
return;
507+
case "email-invitation":
508+
actions.configuration.set_email_invite(settings.get("email_invite"));
509+
return;
510+
case "copy-limit":
511+
actions.configuration.set_copy_parallel(settings.get("copy_parallel"));
512+
return;
513+
case "restrict-student-projects":
514+
actions.configuration.set_student_project_functionality(
515+
completeStudentProjectFunctionality(
516+
settings.get("student_project_functionality")?.toJS() ?? {},
517+
),
518+
);
519+
return;
520+
default:
521+
throw Error(`configuring group ${group} not implemented`);
522+
}
523+
}

src/packages/frontend/course/configuration/customize-student-project-functionality.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Button, Card, Checkbox } from "antd";
77
import { isEqual } from "lodash";
8-
import { useEffect, useState } from "react";
8+
import { useEffect, useRef, useState } from "react";
99
import {
1010
redux,
1111
useIsMountedRef,
@@ -152,6 +152,17 @@ export function CustomizeStudentProjectFunctionality({
152152
}
153153
const isMountedRef = useIsMountedRef();
154154

155+
const lastFunctionalityRef =
156+
useRef<StudentProjectFunctionality>(functionality);
157+
useEffect(() => {
158+
if (isEqual(functionality, lastFunctionalityRef.current)) {
159+
return;
160+
}
161+
// some sort of upstream change
162+
lastFunctionalityRef.current = functionality;
163+
setState(functionality);
164+
}, [functionality]);
165+
155166
function renderOption(option) {
156167
let { title } = option;
157168
if (option.notImplemented) {
@@ -161,7 +172,7 @@ export function CustomizeStudentProjectFunctionality({
161172
<Tip key={title} title={`Disable ${title}`} tip={option.description}>
162173
<Checkbox
163174
disabled={saving}
164-
defaultChecked={state[option.name]}
175+
checked={state[option.name]}
165176
onChange={(e) =>
166177
onChangeState({
167178
[option.name]: (e.target as any).checked,
@@ -228,6 +239,18 @@ export function CustomizeStudentProjectFunctionality({
228239
);
229240
}
230241

242+
export function completeStudentProjectFunctionality(
243+
x: StudentProjectFunctionality,
244+
) {
245+
const y = { ...x };
246+
for (const { name } of OPTIONS) {
247+
if (y[name] == null) {
248+
y[name] = false;
249+
}
250+
}
251+
return y;
252+
}
253+
231254
// NOTE: we allow project_id to be undefined for convenience since some clients
232255
// were written with that unlikely assumption on their knowledge of project_id.
233256
type Hook = (project_id?: string) => StudentProjectFunctionality;

0 commit comments

Comments
 (0)