Skip to content

Commit bff03eb

Browse files
authored
✨ [Frontend] Feature: Share Study via email (#7481)
1 parent f272e95 commit bff03eb

File tree

7 files changed

+173
-14
lines changed

7 files changed

+173
-14
lines changed

services/static-webserver/client/source/class/osparc/auth/core/Utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ qx.Class.define("osparc.auth.core.Utils", {
6868
};
6969
},
7070

71+
checkEmail: function(emailValue) {
72+
// Same expression used in qx.util.Validate.checkEmail
73+
const reg = /^([A-Za-z0-9_\-.+])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,})$/;
74+
return reg.test(emailValue);
75+
},
76+
7177
/** Finds parameters in the fragment
7278
*
7379
* Expected fragment format as https://osparc.io#page=reset-password;code=123546

services/static-webserver/client/source/class/osparc/data/Resources.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ qx.Class.define("osparc.data.Resources", {
276276
method: "PUT",
277277
url: statics.API + "/projects/{studyId}/groups/{gId}"
278278
},
279+
shareWithEmail: {
280+
method: "POST",
281+
url: statics.API + "/projects/{studyId}:share"
282+
},
279283
addTag: {
280284
useCache: false,
281285
method: "POST",

services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", {
5151
this.getChildControl("check");
5252
},
5353

54+
properties: {
55+
iconSrc: {
56+
check: "String",
57+
nullable: true,
58+
init: "@FontAwesome5Solid/check/14",
59+
event: "changeIconSrc"
60+
},
61+
},
62+
5463
members: {
5564
_createChildControlImpl: function(id) {
5665
let control;
@@ -69,7 +78,8 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", {
6978
}
7079
break;
7180
case "check":
72-
control = new qx.ui.basic.Image("@FontAwesome5Solid/check/14");
81+
control = new qx.ui.basic.Image();
82+
this.bind("iconSrc", control, "source");
7383
control.setAnonymous(true);
7484
this._add(control);
7585
this.bind("value", control, "visibility", {

services/static-webserver/client/source/class/osparc/share/AddCollaborators.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ qx.Class.define("osparc.share.AddCollaborators", {
9999
collaboratorsManager.close();
100100
this.fireDataEvent("addCollaborators", e.getData());
101101
}, this);
102+
if (this.__serializedDataCopy["resourceType"] === "study") {
103+
collaboratorsManager.addListener("shareWithEmails", e => {
104+
const {
105+
selectedEmails,
106+
newAccessRights,
107+
message,
108+
} = e.getData();
109+
collaboratorsManager.close();
110+
osparc.store.Study.sendShareEmails(this.__serializedDataCopy, selectedEmails, newAccessRights, message)
111+
.then(() => osparc.FlashMessenger.logAs(this.tr("Emails sent"), "INFO"))
112+
.catch(err => osparc.FlashMessenger.logError(err));
113+
}, this);
114+
}
102115
}, this);
103116

104117
const organizations = this.getChildControl("my-organizations");

services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js

Lines changed: 114 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,23 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", {
3333
this.__potentialCollaborators = {};
3434
this.__reloadPotentialCollaborators();
3535

36+
this.__shareWithEmailEnabled = false;
37+
if (this.__resourceData["resourceType"] === "study") {
38+
osparc.utils.DisabledPlugins.isShareWithEmailEnabled()
39+
.then(isEnabled => {
40+
if (isEnabled) {
41+
this.__shareWithEmailEnabled = true;
42+
}
43+
});
44+
}
45+
3646
this.center();
3747
this.open();
3848
},
3949

4050
events: {
41-
"addCollaborators": "qx.event.type.Data"
51+
"addCollaborators": "qx.event.type.Data",
52+
"shareWithEmails": "qx.event.type.Data",
4253
},
4354

4455
members: {
@@ -65,14 +76,35 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", {
6576
this.add(control);
6677
break;
6778
}
79+
case "filter-layout":
80+
control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
81+
this.add(control);
82+
break;
6883
case "text-filter": {
6984
control = new osparc.filter.TextFilter("name", "collaboratorsManager");
7085
control.setCompact(true);
7186
const filterTextField = control.getChildControl("textfield");
7287
filterTextField.setPlaceholder(this.tr("Search"));
7388
filterTextField.setBackgroundColor("transparent");
7489
this.addListener("appear", () => filterTextField.focus());
75-
this.add(control);
90+
this.getChildControl("filter-layout").add(control, {
91+
flex: 1
92+
});
93+
break;
94+
}
95+
case "send-email-button": {
96+
control = new qx.ui.form.Button(this.tr("Send email"));
97+
control.exclude();
98+
control.addListener("execute", () => {
99+
const textField = this.getChildControl("text-filter").getChildControl("textfield");
100+
const email = textField.getValue();
101+
if (osparc.auth.core.Utils.checkEmail(email)) {
102+
const invitedButton = this.__invitedButton(email);
103+
this.getChildControl("potential-collaborators-list").addAt(invitedButton, 0);
104+
invitedButton.setValue(true);
105+
}
106+
});
107+
this.getChildControl("filter-layout").add(control);
76108
break;
77109
}
78110
case "potential-collaborators-list": {
@@ -143,11 +175,18 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", {
143175
const textFilter = this.getChildControl("text-filter");
144176
const filterTextField = textFilter.getChildControl("textfield");
145177
filterTextField.addListener("input", e => {
146-
const filterValue = e.getData();
178+
const inputValue = e.getData();
147179
if (this.__searchDelayer) {
148180
clearTimeout(this.__searchDelayer);
149181
}
150-
if (filterValue.length > 3) {
182+
const sendEmailButton = this.getChildControl("send-email-button");
183+
sendEmailButton.exclude();
184+
if (inputValue.length > 3) {
185+
if (this.__shareWithEmailEnabled) {
186+
if (osparc.auth.core.Utils.checkEmail(inputValue)) {
187+
sendEmailButton.show();
188+
}
189+
}
151190
const waitBeforeSearching = 1000;
152191
this.__searchDelayer = setTimeout(() => {
153192
this.__searchUsers();
@@ -220,13 +259,10 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", {
220259
this.__addPotentialCollaborators();
221260
},
222261

223-
__collaboratorButton: function(collaborator, preSelected = false) {
262+
__collaboratorButton: function(collaborator) {
224263
const collaboratorButton = new osparc.filter.CollaboratorToggleButton(collaborator);
225264
collaboratorButton.groupId = collaborator.getGroupId();
226-
collaboratorButton.setValue(preSelected);
227-
if (!preSelected) {
228-
collaboratorButton.subscribeToFilterGroup("collaboratorsManager");
229-
}
265+
collaboratorButton.subscribeToFilterGroup("collaboratorsManager");
230266

231267
collaboratorButton.addListener("changeValue", e => {
232268
const selected = e.getData();
@@ -242,6 +278,34 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", {
242278
return collaboratorButton;
243279
},
244280

281+
__invitedButton: function(email) {
282+
if (email in this.__selectedCollaborators) {
283+
return this.__selectedCollaborators[email];
284+
}
285+
286+
const collaboratorData = {
287+
label: email,
288+
email: email,
289+
description: null,
290+
};
291+
const collaborator = qx.data.marshal.Json.createModel(collaboratorData);
292+
const collaboratorButton = new osparc.filter.CollaboratorToggleButton(collaborator);
293+
collaboratorButton.setIconSrc("@FontAwesome5Solid/envelope/14");
294+
295+
collaboratorButton.addListener("changeValue", e => {
296+
const selected = e.getData();
297+
if (selected) {
298+
this.__selectedCollaborators[collaborator.getEmail()] = collaborator;
299+
collaboratorButton.unsubscribeToFilterGroup("collaboratorsManager");
300+
} else if (collaborator.getEmail() in this.__selectedCollaborators) {
301+
delete this.__selectedCollaborators[collaborator.getEmail()];
302+
collaboratorButton.subscribeToFilterGroup("collaboratorsManager");
303+
}
304+
this.getChildControl("share-button").setEnabled(Boolean(Object.keys(this.__selectedCollaborators).length));
305+
}, this);
306+
return collaboratorButton;
307+
},
308+
245309
__addPotentialCollaborators: function(foundCollaborators = []) {
246310
const potentialCollaborators = Object.values(this.__potentialCollaborators).concat(foundCollaborators);
247311
const potentialCollaboratorList = this.getChildControl("potential-collaborators-list");
@@ -315,10 +379,47 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", {
315379
}
316380
}
317381
if (Object.keys(this.__selectedCollaborators).length) {
318-
this.fireDataEvent("addCollaborators", {
319-
selectedGids: Object.keys(this.__selectedCollaborators),
320-
newAccessRights,
321-
});
382+
const selectedGIds = Object.keys(this.__selectedCollaborators).filter(key => qx.lang.Type.isNumber(key));
383+
const selectedEmails = Object.keys(this.__selectedCollaborators).filter(key => osparc.auth.core.Utils.checkEmail(key));
384+
385+
const addCollaborators = () => {
386+
if (selectedGIds.length) {
387+
this.fireDataEvent("addCollaborators", {
388+
selectedGids: selectedGIds,
389+
newAccessRights,
390+
});
391+
}
392+
};
393+
394+
const sendEmails = message => {
395+
if (selectedEmails.length) {
396+
this.fireDataEvent("shareWithEmails", {
397+
selectedEmails,
398+
newAccessRights,
399+
message,
400+
});
401+
}
402+
};
403+
404+
if (selectedEmails.length) {
405+
const dialog = new osparc.ui.window.Confirmation();
406+
dialog.setCaption(this.tr("Add Message"));
407+
dialog.setMessage(this.tr("Add a message to include in the email (optional)"));
408+
dialog.getConfirmButton().setLabel(this.tr("Send"));
409+
const messageEditor = new qx.ui.form.TextArea().set({
410+
autoSize: true,
411+
minHeight: 70,
412+
maxHeight: 140,
413+
});
414+
dialog.addWidget(messageEditor);
415+
dialog.open();
416+
dialog.addListener("close", () => {
417+
addCollaborators();
418+
sendEmails(messageEditor.getValue());
419+
}, this);
420+
} else {
421+
addCollaborators();
422+
}
322423
}
323424
}
324425
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,24 @@ qx.Class.define("osparc.store.Study", {
111111
})
112112
.catch(err => osparc.FlashMessenger.logError(err));
113113
},
114+
115+
sendShareEmails: function(studyData, selectedEmails, newAccessRights, message) {
116+
const promises = selectedEmails.map(selectedEmail => {
117+
const params = {
118+
url: {
119+
"studyId": studyData["uuid"],
120+
},
121+
data: {
122+
shareeEmail: selectedEmail,
123+
sharerMessage: message,
124+
read: newAccessRights["read"],
125+
write: newAccessRights["write"],
126+
delete: newAccessRights["delete"],
127+
}
128+
};
129+
return osparc.data.Resources.fetch("studies", "shareWithEmail", params);
130+
});
131+
return Promise.all(promises);
132+
},
114133
}
115134
});

services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ qx.Class.define("osparc.utils.DisabledPlugins", {
2929
VERSION_CONTROL: "WEBSERVER_VERSION_CONTROL",
3030
META_MODELING: "WEBSERVER_META_MODELING",
3131
LICENSES: "WEBSERVER_LICENSES",
32+
SHARE_WITH_EMAIL: "WEBSERVER_SHARE_WITH_EMAIL",
3233

3334
isExportDisabled: function() {
3435
return this.__isPluginDisabled(this.EXPORT);
@@ -52,6 +53,11 @@ qx.Class.define("osparc.utils.DisabledPlugins", {
5253
return this.__isPluginDisabled(this.LICENSES);
5354
},
5455

56+
isShareWithEmailEnabled: function() {
57+
// return !this.__isPluginDisabled(this.SHARE_WITH_EMAIL);
58+
return new Promise(resolve => resolve(osparc.utils.Utils.isDevelopmentPlatform()));
59+
},
60+
5561
isJobsEnabled: function() {
5662
if (osparc.utils.Utils.isDevelopmentPlatform() && osparc.product.Utils.isProduct("s4lacad")) {
5763
return true;

0 commit comments

Comments
 (0)