Skip to content

Commit 826090b

Browse files
authored
Merge branch 'master' into fix/catalog-batch-service-follow-up
2 parents d08a429 + b01bc0d commit 826090b

File tree

5 files changed

+324
-3
lines changed

5 files changed

+324
-3
lines changed

services/static-webserver/client/source/class/osparc/conversation/AddMessage.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,28 @@ qx.Class.define("osparc.conversation.AddMessage", {
131131
control.addListener("execute", this.__addCommentPressed, this);
132132
this.getChildControl("add-comment-layout").add(control);
133133
break;
134+
case "footer-layout":
135+
control = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({
136+
alignY: "middle"
137+
}));
138+
this._add(control);
139+
break;
140+
case "no-permission-label":
141+
control = new qx.ui.basic.Label(this.tr("Only users with write access can add comments.")).set({
142+
allowGrowX: true,
143+
});
144+
this.getChildControl("footer-layout").addAt(control, 0, {
145+
flex: 1
146+
});
147+
break;
134148
case "notify-user-button":
135149
control = new qx.ui.form.Button("🔔 " + this.tr("Notify user")).set({
136150
appearance: "form-button",
137151
allowGrowX: false,
138152
alignX: "right",
139153
});
140154
control.addListener("execute", () => this.__notifyUserTapped());
141-
this._add(control);
155+
this.getChildControl("footer-layout").addAt(control, 1);
142156
break;
143157
}
144158

@@ -152,13 +166,16 @@ qx.Class.define("osparc.conversation.AddMessage", {
152166
},
153167

154168
__applyStudyData: function(studyData) {
169+
const noPermissionLabel = this.getChildControl("no-permission-label");
155170
const notifyUserButton = this.getChildControl("notify-user-button");
156171
if (studyData) {
157172
const canIWrite = osparc.data.model.Study.canIWrite(studyData["accessRights"])
158173
this.getChildControl("add-comment-button").setEnabled(canIWrite);
174+
noPermissionLabel.setVisibility(canIWrite ? "hidden" : "visible");
159175
notifyUserButton.show();
160176
notifyUserButton.setEnabled(canIWrite);
161177
} else {
178+
noPermissionLabel.hide();
162179
notifyUserButton.exclude();
163180
}
164181
},

services/static-webserver/client/source/class/osparc/desktop/account/ProfilePage.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
3838
}
3939
this._add(this.__createPasswordSection());
4040
this._add(this.__createContactSection());
41+
this._add(this.__createTransferProjectsSection());
4142
this._add(this.__createDeleteAccount());
4243

4344
this.__userProfileData = {};
@@ -653,6 +654,26 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
653654
return box;
654655
},
655656

657+
__createTransferProjectsSection: function() {
658+
const box = this.self().createSectionBox(this.tr("Transfer Projects"));
659+
box.addHelper(this.tr("Transfer of your projects to another user."));
660+
661+
const transferBtn = new qx.ui.form.Button(this.tr("Transfer Projects")).set({
662+
appearance: "strong-button",
663+
alignX: "right",
664+
allowGrowX: false
665+
});
666+
transferBtn.addListener("execute", () => {
667+
const transferProjects = new osparc.desktop.account.TransferProjects();
668+
const win = osparc.ui.window.Window.popUpInWindow(transferProjects, qx.locale.Manager.tr("Transfer Projects"), 500, null);
669+
transferProjects.addListener("cancel", () => win.close());
670+
transferProjects.addListener("transferred", () => win.close());
671+
});
672+
box.add(transferBtn);
673+
674+
return box;
675+
},
676+
656677
__createDeleteAccount: function() {
657678
// layout
658679
const box = this.self().createSectionBox(this.tr("Delete Account"));
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.desktop.account.TransferProjects", {
19+
extend: qx.ui.core.Widget,
20+
21+
construct: function() {
22+
this.base(arguments, this.tr("Transfer Projects"));
23+
24+
this._setLayout(new qx.ui.layout.VBox(15));
25+
26+
this.__buildLayout();
27+
},
28+
29+
events: {
30+
"transferred": "qx.event.type.Event",
31+
"cancel": "qx.event.type.Event"
32+
},
33+
34+
properties: {
35+
targetUser: {
36+
check: "osparc.data.model.User",
37+
init: null,
38+
nullable: true,
39+
event: "changeTargetUser",
40+
},
41+
},
42+
43+
members: {
44+
_createChildControlImpl: function(id) {
45+
let control = null;
46+
switch (id) {
47+
case "intro-text": {
48+
const text = this.tr(`\
49+
You are about to transfer all your projects to another user.<br>
50+
There are two ways to do so:<br>
51+
- Share all your projects with the target user and keep the co-ownership. <br>
52+
- Share all your projects with the target user and remove yourself as co-owner. <br>
53+
`);
54+
control = new qx.ui.basic.Label().set({
55+
value: text,
56+
font: "text-14",
57+
rich: true,
58+
wrap: true
59+
});
60+
this._add(control);
61+
break;
62+
}
63+
case "target-user-layout": {
64+
control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({
65+
alignY: "middle",
66+
}));
67+
const label = new qx.ui.basic.Label(this.tr("Target user:")).set({
68+
font: "text-14"
69+
});
70+
control.add(label);
71+
this._add(control);
72+
break;
73+
}
74+
case "target-user-button":
75+
control = new qx.ui.form.Button(this.tr("Select user")).set({
76+
appearance: "strong-button",
77+
allowGrowX: false,
78+
});
79+
this.bind("targetUser", control, "label", {
80+
converter: targetUser => targetUser ? targetUser.getUserName() : this.tr("Select user")
81+
});
82+
control.addListener("execute", () => this.__selectTargetUserTapped(), this);
83+
this.getChildControl("target-user-layout").add(control);
84+
break;
85+
case "buttons-container":
86+
control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({
87+
alignX: "right"
88+
}));
89+
this._add(control);
90+
break;
91+
case "cancel-button":
92+
control = new qx.ui.form.Button(this.tr("Cancel")).set({
93+
appearance: "form-button-text",
94+
allowGrowX: false,
95+
});
96+
control.addListener("execute", () => this.fireEvent("cancel"), this);
97+
this.getChildControl("buttons-container").add(control);
98+
break;
99+
case "share-and-keep-button":
100+
control = new osparc.ui.form.FetchButton(this.tr("Share and keep ownership")).set({
101+
appearance: "strong-button",
102+
allowGrowX: false,
103+
});
104+
this.bind("targetUser", control, "enabled", {
105+
converter: targetUser => targetUser !== null
106+
});
107+
control.addListener("execute", () => this.__shareAndKeepOwnership(), this);
108+
this.getChildControl("buttons-container").add(control);
109+
break;
110+
case "share-and-leave-button": {
111+
control = new osparc.ui.form.FetchButton(this.tr("Share and remove my ownership")).set({
112+
appearance: "danger-button",
113+
allowGrowX: false,
114+
});
115+
this.bind("targetUser", control, "enabled", {
116+
converter: targetUser => targetUser !== null
117+
});
118+
control.addListener("execute", () => this.__shareAndLeaveOwnership(), this);
119+
this.getChildControl("buttons-container").add(control);
120+
break;
121+
}
122+
}
123+
return control || this.base(arguments, id);
124+
},
125+
126+
__buildLayout: function() {
127+
this.getChildControl("intro-text");
128+
this.getChildControl("target-user-button");
129+
this.getChildControl("cancel-button");
130+
this.getChildControl("share-and-keep-button");
131+
this.getChildControl("share-and-leave-button");
132+
},
133+
134+
__selectTargetUserTapped: function() {
135+
const collaboratorsManager = new osparc.share.NewCollaboratorsManager({}, false, false).set({
136+
acceptOnlyOne: true
137+
});
138+
collaboratorsManager.getChildControl("intro-text").set({
139+
value: this.tr("Select the user you want to transfer all your projects to.")
140+
});
141+
collaboratorsManager.setCaption(this.tr("Select target user"));
142+
collaboratorsManager.addListener("addCollaborators", e => {
143+
collaboratorsManager.close();
144+
const selectedUsers = e.getData();
145+
if (
146+
selectedUsers &&
147+
selectedUsers["selectedGids"] &&
148+
selectedUsers["selectedGids"].length === 1
149+
) {
150+
osparc.store.Users.getInstance().getUser(selectedUsers["selectedGids"][0])
151+
.then(user => {
152+
if (user.getGroupId() !== osparc.store.Groups.getInstance().getMyGroupId()) {
153+
this.setTargetUser(user);
154+
} else {
155+
osparc.FlashMessenger.logAs(this.tr("You cannot transfer projects to yourself"), "ERROR");
156+
}
157+
});
158+
}
159+
}, this);
160+
},
161+
162+
__shareAndKeepOwnership: function() {
163+
this.setEnabled(false);
164+
this.getChildControl("share-and-keep-button").setFetching(true);
165+
this.__shareAllProjects()
166+
.then(() => {
167+
const msg = this.tr("All projects have been shared with the target user. You still own them.");
168+
osparc.FlashMessenger.logAs(msg, "INFO", 10000);
169+
this.fireEvent("transferred");
170+
})
171+
.catch(err => {
172+
console.error(err);
173+
osparc.FlashMessenger.logError(err);
174+
})
175+
.finally(() => {
176+
this.setEnabled(true);
177+
this.getChildControl("share-and-keep-button").setFetching(false);
178+
});
179+
},
180+
181+
__shareAndLeaveOwnership: function() {
182+
osparc.FlashMessenger.logAs(this.tr("This option is not yet enabled."), "WARNING", 10000);
183+
return;
184+
185+
this.setEnabled(false);
186+
this.getChildControl("share-and-leave-button").setFetching(true);
187+
this.__shareAllProjects()
188+
.then(allMyStudies => {
189+
return this.__removeMyOwnerships(allMyStudies);
190+
})
191+
.then(() => {
192+
const msg = this.tr("All projects have been shared with the target user and you have been removed as co-owner.");
193+
osparc.FlashMessenger.logAs(msg, "INFO", 10000);
194+
this.fireEvent("transferred");
195+
})
196+
.catch(err => {
197+
console.error(err);
198+
osparc.FlashMessenger.logError(err);
199+
})
200+
.finally(() => {
201+
this.setEnabled(true);
202+
this.getChildControl("share-and-leave-button").setFetching(false);
203+
});
204+
},
205+
206+
__filterMyOwnedStudies: function(allMyReadStudies) {
207+
// filter those that I don't own (no delete right)
208+
const myGroupId = osparc.store.Groups.getInstance().getMyGroupId();
209+
const ownerAccess = osparc.data.Roles.STUDY["delete"].accessRights;
210+
const allMyStudies = allMyReadStudies.filter(studyData => {
211+
return (
212+
myGroupId in studyData["accessRights"] &&
213+
JSON.stringify(studyData["accessRights"][myGroupId]) === JSON.stringify(ownerAccess)
214+
)
215+
});
216+
return allMyStudies;
217+
},
218+
219+
__shareAllProjects: function() {
220+
const targetUser = this.getTargetUser();
221+
if (targetUser === null) {
222+
return;
223+
}
224+
const targetGroupId = targetUser.getGroupId();
225+
226+
return osparc.store.Study.getInstance().getAllMyStudies()
227+
.then(allMyReadStudies => {
228+
// filter those that I don't own (no delete right)
229+
const allMyStudies = this.__filterMyOwnedStudies(allMyReadStudies);
230+
const ownerAccess = osparc.data.Roles.STUDY["delete"].accessRights;
231+
const newAccessRights = {
232+
[targetGroupId]: ownerAccess
233+
};
234+
const promises = [];
235+
allMyStudies.forEach(studyData => {
236+
// first check it's not already shared with the target user
237+
if (targetGroupId in studyData["accessRights"]) {
238+
if (JSON.stringify(studyData["accessRights"][targetGroupId]) !== JSON.stringify(ownerAccess)) {
239+
// update access rights to owner
240+
promises.push(osparc.store.Study.getInstance().updateCollaborator(studyData, targetGroupId, ownerAccess));
241+
}
242+
} else {
243+
// add as new collaborator with owner rights
244+
promises.push(osparc.store.Study.getInstance().addCollaborators(studyData, newAccessRights));
245+
}
246+
});
247+
// return only those projects that were shared
248+
return Promise.all(promises)
249+
.then(() => {
250+
return allMyStudies;
251+
})
252+
.catch(err => {
253+
console.error("Error sharing projects:", err);
254+
});
255+
});
256+
},
257+
258+
__removeMyOwnerships: function(studies) {
259+
const myGroupId = osparc.store.Groups.getInstance().getMyGroupId();
260+
const promises = studies.map(study => {
261+
return osparc.store.Study.getInstance().removeCollaborator(study, myGroupId);
262+
});
263+
return Promise.all(promises);
264+
},
265+
}
266+
});

services/static-webserver/client/source/class/osparc/info/StudyLarge.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ qx.Class.define("osparc.info.StudyLarge", {
6060
if (
6161
this.__canIWrite() &&
6262
this.getStudy().getTemplateType() &&
63-
osparc.data.Permissions.getInstance().isProductOwner()
63+
osparc.data.Permissions.getInstance().isTester()
6464
) {
65-
// let product owners change the template type
65+
// let testers change the template type
6666
const hBox = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({
6767
alignY: "middle",
6868
}));

services/static-webserver/client/source/class/osparc/store/Study.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ qx.Class.define("osparc.store.Study", {
6363
return osparc.data.Resources.fetch("studies", "getOne", params)
6464
},
6565

66+
getAllMyStudies: function() {
67+
const params = {
68+
url: {
69+
orderBy: JSON.stringify({
70+
field: "last_change_date",
71+
direction: "desc"
72+
}),
73+
text: "",
74+
}
75+
};
76+
// getPageSearch with no text filter returns all studies
77+
return osparc.data.Resources.getInstance().getAllPages("studies", params, "getPageSearch")
78+
.then(allStudies => {
79+
return allStudies;
80+
});
81+
},
82+
6683
openStudy: function(studyId, autoStart = true) {
6784
const params = {
6885
url: {

0 commit comments

Comments
 (0)