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/project-upgrades.ts
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
Functions for determining various things about applying upgrades to a project.
8
9
WARNING: This should stay as simple typescript with no crazy dependencies for easy node.js unit testing.
10
*/
11
12
import { copy, keys, map_diff, map_sum } from "@cocalc/util/misc";
13
import { ProjectMap } from "../projects/store";
14
15
interface ExistenceMap {
16
[keys: string]: boolean;
17
}
18
19
export function available_upgrades(opts: {
20
account_id: string; // id of a user
21
purchased_upgrades: object; // map of the total upgrades purchased by account_id
22
project_map: ProjectMap; // immutable.js map of data about projects
23
student_project_ids: ExistenceMap; // map project_id:true with keys *all* student
24
// projects in course, including deleted
25
}) {
26
/*
27
Return the total upgrades that the user with given account_id has to apply
28
toward this course. This is all upgrades they have purchased minus
29
upgrades they have applied to projects that aren't student projects in
30
this course. Thus this is what they have available to distribute to
31
their students in this course.
32
33
This is a map {quota0:x, quota1:y, ...}
34
*/
35
let available = copy(opts.purchased_upgrades);
36
opts.project_map.forEach(function (project, project_id) {
37
if (opts.student_project_ids[project_id]) {
38
// do not count projects in course
39
return;
40
}
41
const upgrades = project.getIn([
42
"users",
43
opts.account_id,
44
"upgrades",
45
]) as any;
46
if (upgrades != null) {
47
available = map_diff(available as any, upgrades.toJS());
48
}
49
});
50
return available;
51
}
52
53
export function current_student_project_upgrades(opts: {
54
account_id: string; // id of a user
55
project_map: ProjectMap; // immutable.js map of data about projects
56
student_project_ids: ExistenceMap; // map project_id:true with keys *all* student
57
}) {
58
/*
59
Return the total upgrades currently applied to each student project from
60
everybody else except the user with given account_id.
61
62
This output is a map {project_id:{quota0:x, quota1:y, ...}, ...}; only projects with
63
actual upgrades are included.
64
*/
65
const other = {};
66
for (const project_id in opts.student_project_ids) {
67
const users = opts.project_map.getIn([project_id, "users"]) as any;
68
if (users == null) {
69
continue;
70
}
71
var x: any = undefined;
72
users.forEach(function (info, user_id) {
73
if (user_id === opts.account_id) {
74
return;
75
}
76
const upgrades = info.get("upgrades");
77
if (upgrades == null) {
78
return;
79
}
80
x = map_sum(upgrades.toJS(), x != null ? x : {});
81
});
82
if (x != null) {
83
other[project_id] = x;
84
}
85
}
86
return other;
87
}
88
89
export function upgrade_plan(opts: {
90
account_id: string; // id of a user
91
purchased_upgrades: object; // map of the total upgrades purchased by account_id
92
project_map: ProjectMap; // immutable.js map of data about projects
93
student_project_ids: ExistenceMap; // map project_id:true with keys *all* student
94
// projects in course, including deleted
95
deleted_project_ids: ExistenceMap; // map project_id:true just for projects where
96
// student is considered deleted from class
97
upgrade_goal: object; // [quota0:x, quota1:y]
98
}) {
99
/*
100
Determine what upgrades should be applied by this user to get
101
the student projects to the given upgrade goal. Preference
102
is by project_id in order (arbitrary, but stable).
103
104
The output is a map {student_project_id:{quota0:x, quota1:y, ...}, ...}, where the quota0:x means
105
that account_id will apply x amount of quota0 total. Thus to actually *do* the upgrading,
106
this user (account_id) would go through the project map and set their upgrade contribution
107
for the student projects in this course to exactly what is specified by this function.
108
Note that no upgrade quota will be deducted from projects outside this course to satisfy
109
the upgrade_goal.
110
111
If a student_project_id is missing from the output the contribution is 0; if a quota is
112
missing, the contribution is 0.
113
114
The keys of the output map are **exactly** the ids of the projects where the current
115
allocation should be *changed*. That said, we only consider quotas explicitly given
116
in the upgrade_goal map.
117
*/
118
// upgrades, etc., that student projects already have (which account_id did not provide)
119
const cur = current_student_project_upgrades({
120
account_id: opts.account_id,
121
project_map: opts.project_map,
122
student_project_ids: opts.student_project_ids,
123
});
124
125
// upgrades we have that have not been allocated to our course
126
const available = available_upgrades({
127
account_id: opts.account_id,
128
purchased_upgrades: opts.purchased_upgrades,
129
project_map: opts.project_map,
130
student_project_ids: opts.student_project_ids,
131
});
132
133
const ids = keys(opts.student_project_ids);
134
ids.sort();
135
const plan = {};
136
for (const project_id of ids) {
137
if (opts.deleted_project_ids[project_id]) {
138
// give this project NOTHING
139
continue;
140
}
141
plan[project_id] = {};
142
// we only care about quotas in the upgrade_goal
143
for (var quota in opts.upgrade_goal) {
144
const val = opts.upgrade_goal[quota];
145
const need =
146
val -
147
((cur[project_id] != null ? cur[project_id][quota] : undefined) != null
148
? cur[project_id] != null
149
? cur[project_id][quota]
150
: undefined
151
: 0);
152
if (need > 0) {
153
const have = Math.min(need, available[quota]);
154
plan[project_id][quota] = have;
155
available[quota] -= have;
156
}
157
}
158
// is there an actual allocation change? if not, we do not include this key.
159
const upgrades = opts.project_map.getIn([
160
project_id,
161
"users",
162
opts.account_id,
163
"upgrades",
164
]) as any;
165
const alloc = upgrades != null ? upgrades.toJS() : {};
166
let change = false;
167
for (quota in opts.upgrade_goal) {
168
if (
169
(alloc[quota] != null ? alloc[quota] : 0) !==
170
(plan[project_id][quota] != null ? plan[project_id][quota] : 0)
171
) {
172
change = true;
173
break;
174
}
175
}
176
if (!change) {
177
delete plan[project_id];
178
}
179
}
180
return plan;
181
}
182
183