Skip to content

Commit eca0268

Browse files
authored
✨FE: Expiration date (ITISFoundation#3349)
1 parent 0cc9c76 commit eca0268

File tree

8 files changed

+170
-62
lines changed

8 files changed

+170
-62
lines changed

services/web/client/source/class/osparc/Application.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,34 @@ qx.Class.define("osparc.Application", {
350350
// Invalidate the entire cache
351351
osparc.store.Store.getInstance().invalidate();
352352

353-
if (studyId) {
354-
osparc.store.Store.getInstance().setCurrentStudyId(studyId);
355-
}
356-
this.__connectWebSocket();
357-
const mainPage = this.__mainPage = new osparc.desktop.MainPage();
358-
this.__loadView(mainPage);
353+
osparc.data.Resources.getOne("profile")
354+
.then(profile => {
355+
if ("expirationDate" in profile) {
356+
const now = new Date();
357+
const daysToExpiration = osparc.utils.Utils.daysBetween(now, new Date(profile["expirationDate"]));
358+
console.log("daysToExpiration", daysToExpiration);
359+
if (daysToExpiration < 1) {
360+
// Will not get here. Backend should not allow
361+
let msg = this.tr("This account is expired.<br>");
362+
msg += this.tr("Please, contact us by email:<br>");
363+
osparc.store.StaticInfo.getInstance().getSupportEmail()
364+
.then(supportEmail => osparc.component.message.FlashMessenger.getInstance().logAs(msg+supportEmail, "ERROR"));
365+
this.logout();
366+
return;
367+
} else if (daysToExpiration < 7) {
368+
let msg = this.tr("This account will expire in ") + daysToExpiration + this.tr(" days<br>");
369+
msg += this.tr("Please, contact us by email:<br>");
370+
osparc.store.StaticInfo.getInstance().getSupportEmail()
371+
.then(supportEmail => osparc.component.message.FlashMessenger.getInstance().logAs(msg+supportEmail, "WARNING"));
372+
}
373+
}
374+
if (studyId) {
375+
osparc.store.Store.getInstance().setCurrentStudyId(studyId);
376+
}
377+
this.__connectWebSocket();
378+
const mainPage = this.__mainPage = new osparc.desktop.MainPage();
379+
this.__loadView(mainPage);
380+
});
359381
},
360382

361383
__loadNodeViewerPage: function(studyId, viewerNodeId) {

services/web/client/source/class/osparc/auth/Data.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ qx.Class.define("osparc.auth.Data", {
6868
init: null,
6969
nullable: true,
7070
check: "String"
71+
},
72+
73+
firstName: {
74+
init: "",
75+
nullable: true,
76+
check: "String",
77+
event: "changeFirstName"
78+
},
79+
80+
lastName: {
81+
init: "",
82+
nullable: true,
83+
check: "String"
7184
}
7285
},
7386

@@ -90,7 +103,11 @@ qx.Class.define("osparc.auth.Data", {
90103
},
91104

92105
getUserName: function() {
93-
const email = osparc.auth.Data.getInstance().getEmail();
106+
const firstName = this.getFirstName();
107+
if (firstName) {
108+
return firstName;
109+
}
110+
const email = this.getEmail();
94111
if (email) {
95112
return osparc.utils.Utils.getNameFromEmail(email);
96113
}

services/web/client/source/class/osparc/auth/Manager.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,27 @@ qx.Class.define("osparc.auth.Manager", {
213213
.catch(err => failCbk.call(context, err.message));
214214
},
215215

216+
updateProfile: function(profile) {
217+
const authData = osparc.auth.Data.getInstance();
218+
authData.set({
219+
email: profile.login,
220+
firstName: profile.first_name,
221+
lastName: profile.last_name
222+
});
223+
},
224+
216225
__loginUser: function(profile) {
217-
osparc.auth.Data.getInstance().setEmail(profile.login);
218-
osparc.auth.Data.getInstance().setToken(profile.login);
219-
osparc.auth.Data.getInstance().setUserId(profile.id);
220-
osparc.auth.Data.getInstance().setGroupId(profile["groups"]["me"]["gid"]);
226+
const authData = osparc.auth.Data.getInstance();
227+
authData.set({
228+
token: profile.login,
229+
userId: profile.id,
230+
groupId: profile["groups"]["me"]["gid"]
231+
});
232+
this.updateProfile(profile);
221233
if ("organizations" in profile["groups"]) {
222234
const orgIds = [];
223235
profile["groups"]["organizations"].forEach(org => orgIds.push(org["gid"]));
224-
osparc.auth.Data.getInstance().setOrgIds(orgIds);
236+
authData.setOrgIds(orgIds);
225237
}
226238
const role = profile.role.toLowerCase();
227239
osparc.data.Permissions.getInstance().setRole(role);

services/web/client/source/class/osparc/desktop/preferences/pages/ProfilePage.js

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ qx.Class.define("osparc.desktop.preferences.pages.ProfilePage", {
3333
this.__userProfileData = null;
3434
this.__userProfileModel = null;
3535

36-
this.__getValuesFromServer();
36+
this.__getProfile();
3737

3838
this.add(this.__createProfileUser());
3939
},
@@ -87,6 +87,20 @@ qx.Class.define("osparc.desktop.preferences.pages.ProfilePage", {
8787

8888
box.add(new qx.ui.form.renderer.Single(form));
8989

90+
const expirationLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)).set({
91+
paddingLeft: 16,
92+
visibility: "excluded"
93+
});
94+
expirationLayout.add(new qx.ui.basic.Label(this.tr("Expiration date:")));
95+
const expirationDate = new qx.ui.basic.Label();
96+
expirationLayout.add(expirationDate);
97+
const infoLabel = this.tr("Please, contact us by email:<br>");
98+
const infoExtension = new osparc.ui.hint.InfoHint(infoLabel);
99+
osparc.store.StaticInfo.getInstance().getSupportEmail()
100+
.then(supportEmail => infoExtension.setHintText(infoLabel + supportEmail));
101+
expirationLayout.add(infoExtension);
102+
box.add(expirationLayout);
103+
90104
const img = new qx.ui.basic.Image().set({
91105
decorator: new qx.ui.decoration.Decorator().set({
92106
radius: 50
@@ -100,16 +114,16 @@ qx.Class.define("osparc.desktop.preferences.pages.ProfilePage", {
100114
"firstName": null,
101115
"lastName": null,
102116
"email": null,
103-
"role": null
117+
"role": null,
118+
"expirationDate": null
104119
};
105120

106121
if (qx.core.Environment.get("qx.debug")) {
107-
raw = {
108-
"firstName": "Bizzy",
109-
"lastName": "Zastrow",
110-
"email": "[email protected]",
111-
"role": "Tester"
112-
};
122+
raw.firstName = "Bizzy";
123+
raw.lastName = "Zastrow";
124+
raw.email = "[email protected]";
125+
raw.role = "User";
126+
raw.expirationDate = null;
113127
}
114128
const model = this.__userProfileModel = qx.data.marshal.Json.createModel(raw);
115129
const controller = new qx.data.controller.Object(model);
@@ -122,69 +136,95 @@ qx.Class.define("osparc.desktop.preferences.pages.ProfilePage", {
122136
});
123137
controller.addTarget(lastName, "value", "lastName", true);
124138
controller.addTarget(role, "value", "role", false);
139+
controller.addTarget(expirationDate, "value", "expirationDate", false, {
140+
converter: data => {
141+
if (data) {
142+
expirationLayout.show();
143+
return osparc.utils.Utils.formatDateAndTime(new Date(data));
144+
}
145+
return "";
146+
}
147+
});
125148
controller.addTarget(img, "source", "email", false, {
126149
converter: function(data) {
127150
return osparc.utils.Avatar.getUrl(email.getValue(), 150);
128151
}
129152
});
130153

131154
// validation
132-
const manager = new qx.ui.form.validation.Manager();
133-
manager.add(email, qx.util.Validate.email());
134-
[firstName, lastName].forEach(field => {
135-
manager.add(field, qx.util.Validate.regExp(/[^\.\d]+/), this.tr("Avoid dots or numbers in text"));
136-
});
155+
const emailValidator = new qx.ui.form.validation.Manager();
156+
emailValidator.add(email, qx.util.Validate.email());
157+
158+
const namesValidator = new qx.ui.form.validation.Manager();
159+
namesValidator.add(firstName, qx.util.Validate.regExp(/[^\.\d]+/), this.tr("Avoid dots or numbers in text"));
160+
namesValidator.add(lastName, qx.util.Validate.regExp(/^$|[^\.\d]+/), this.tr("Avoid dots or numbers in text")); // allow also emtpy last name
137161

138162
const updateBtn = new qx.ui.form.Button("Update Profile").set({
139163
allowGrowX: false
140164
});
141165
box.add(updateBtn);
142166

143-
// update trigger
144167
updateBtn.addListener("execute", () => {
145168
if (!osparc.data.Permissions.getInstance().canDo("user.user.update", true)) {
146169
this.__resetDataToModel();
147170
return;
148171
}
149172

150-
if (manager.validate()) {
151-
const emailReq = new osparc.io.request.ApiRequest("/auth/change-email", "POST");
152-
emailReq.setRequestData({
153-
"email": model.getEmail()
154-
});
155-
156-
const profileReq = new osparc.io.request.ApiRequest("/me", "PUT");
157-
profileReq.setRequestData({
158-
"first_name": model.getFirstName(),
159-
"last_name": model.getLastName()
160-
});
161-
162-
[emailReq, profileReq].forEach(req => {
163-
// requests
164-
req.addListenerOnce("success", e => {
165-
const res = e.getTarget().getResponse();
166-
if (res && res.data) {
167-
osparc.component.message.FlashMessenger.getInstance().log(res.data);
168-
}
169-
}, this);
170-
171-
req.addListenerOnce("fail", e => {
172-
// FIXME: should revert to old?? or GET? Store might resolve this??
173-
this.__resetDataToModel();
174-
const error = e.getTarget().getResponse().error;
175-
const msg = error ? error["errors"][0].message : this.tr("Failed to update profile");
176-
osparc.component.message.FlashMessenger.getInstance().logAs(msg, "ERROR");
177-
}, this);
178-
179-
req.send();
180-
});
173+
const requests = {
174+
email: null,
175+
names: null
176+
};
177+
if (this.__userProfileData["login"] !== model.getEmail()) {
178+
if (emailValidator.validate()) {
179+
const emailReq = new osparc.io.request.ApiRequest("/auth/change-email", "POST");
180+
emailReq.setRequestData({
181+
"email": model.getEmail()
182+
});
183+
requests.email = emailReq;
184+
}
185+
}
186+
187+
if (this.__userProfileData["first_name"] !== model.getFirstName() || this.__userProfileData["last_name"] !== model.getLastName()) {
188+
if (namesValidator.validate()) {
189+
const profileReq = new osparc.io.request.ApiRequest("/me", "PUT");
190+
profileReq.setRequestData({
191+
"first_name": model.getFirstName(),
192+
"last_name": model.getLastName()
193+
});
194+
requests.names = profileReq;
195+
}
181196
}
197+
198+
Object.keys(requests).forEach(key => {
199+
const req = requests[key];
200+
if (req === null) {
201+
return;
202+
}
203+
204+
req.addListenerOnce("success", e => {
205+
const reqData = e.getTarget().getRequestData();
206+
this.__setDataToModel(Object.assign(this.__userProfileData, reqData));
207+
osparc.auth.Manager.getInstance().updateProfile(this.__userProfileData);
208+
const res = e.getTarget().getResponse();
209+
const msg = (res && res.data) ? res.data : this.tr("Profile updated");
210+
osparc.component.message.FlashMessenger.getInstance().logAs(msg, "INFO");
211+
}, this);
212+
213+
req.addListenerOnce("fail", e => {
214+
this.__resetDataToModel();
215+
const error = e.getTarget().getResponse().error;
216+
const msg = error ? error["errors"][0].message : this.tr("Failed to update profile");
217+
osparc.component.message.FlashMessenger.getInstance().logAs(msg, "ERROR");
218+
}, this);
219+
220+
req.send();
221+
});
182222
}, this);
183223

184224
return box;
185225
},
186226

187-
__getValuesFromServer: function() {
227+
__getProfile: function() {
188228
osparc.data.Resources.getOne("profile", {}, null, false)
189229
.then(profile => {
190230
this.__setDataToModel(profile);
@@ -201,7 +241,8 @@ qx.Class.define("osparc.desktop.preferences.pages.ProfilePage", {
201241
"firstName": data["first_name"] || "",
202242
"lastName": data["last_name"] || "",
203243
"email": data["login"],
204-
"role": data["role"] || ""
244+
"role": data["role"] || "",
245+
"expirationDate": data["expirationDate"] || null
205246
});
206247
}
207248
},

services/web/client/source/class/osparc/navigation/UserMenuButton.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ qx.Class.define("osparc.navigation.UserMenuButton", {
2222
this.base(arguments);
2323

2424
const userEmail = osparc.auth.Data.getInstance().getEmail() || "[email protected]";
25-
const userName = osparc.auth.Data.getInstance().getUserName() || "bizzy";
2625
const menu = new qx.ui.menu.Menu().set({
2726
font: "text-14"
2827
});
2928
this.set({
3029
font: "text-14",
3130
icon: osparc.utils.Avatar.getUrl(userEmail, 32),
32-
label: userName,
31+
label: "bizzy",
3332
menu
3433
});
34+
osparc.auth.Data.getInstance().bind("firstName", this, "label");
3535
osparc.utils.Utils.setIdToWidget(this, "userMenuMainBtn");
3636

3737
this.getChildControl("icon").getContentElement().setStyles({

services/web/client/source/class/osparc/store/StaticInfo.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ qx.Class.define("osparc.store.StaticInfo", {
4545
getDisplayNameKey: function() {
4646
const productName = osparc.utils.Utils.getProductName();
4747
return productName + "DisplayName";
48+
},
49+
50+
getSupportEmail: function() {
51+
const productName = osparc.utils.Utils.getProductName();
52+
const supportEmailKey = productName + "SupportEmail";
53+
return this.getValue(supportEmailKey);
4854
}
4955
}
5056
});

services/web/client/source/class/osparc/utils/Utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,15 @@ qx.Class.define("osparc.utils.Utils", {
274274
return osparc.utils.Utils.formatDate(value) + " " + osparc.utils.Utils.formatTime(value);
275275
},
276276

277+
daysBetween: function(date1, date2) {
278+
// The number of milliseconds in one day
279+
const ONE_DAY = 1000 * 60 * 60 * 24;
280+
// Calculate the difference in milliseconds
281+
const differenceMs = date2 - date1;
282+
// Convert back to days and return
283+
return Math.round(differenceMs / ONE_DAY);
284+
},
285+
277286
getNameFromEmail: function(email) {
278287
return email.split("@")[0];
279288
},

tests/e2e/tutorials/ti-plan.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ async function runTutorial() {
111111

112112
const outFiles = [
113113
"temp_ti_field.cache",
114-
"TIP_report.pdf"
114+
"TIP_report.pdf",
115+
"table.csv"
115116
];
116117
await tutorial.checkNodeOutputsAppMode(workbenchData["nodeIds"][2], outFiles, true, false);
117118

0 commit comments

Comments
 (0)