Skip to content

🎨 [Frontend] Feature: Hide username #7406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 56 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
ce0a606
drafts test
pcrespov Mar 20, 2025
d5f2e7e
privacy
pcrespov Mar 20, 2025
47d8bc5
migration
pcrespov Mar 20, 2025
c7b2357
update OAS
pcrespov Mar 20, 2025
badef8f
services/webserver api version: 0.61.2 → 0.61.3
pcrespov Mar 20, 2025
8e89260
fixes tests
pcrespov Mar 20, 2025
afe375c
cleanuppre
pcrespov Mar 20, 2025
c672b55
fixes test
pcrespov Mar 20, 2025
e72b5d5
fixes test
pcrespov Mar 20, 2025
d2fbd04
helpers
pcrespov Mar 20, 2025
ef5f72a
self test
pcrespov Mar 20, 2025
8eb1612
fixes test
pcrespov Mar 20, 2025
ae47f89
Merge branch 'master' into is38/username-privacy
pcrespov Mar 21, 2025
3431e8d
hideUsername field and message
odeimaiz Mar 21, 2025
f665a5c
Merge branch 'master' into feature/hide-username
odeimaiz Mar 21, 2025
2057d34
optOutMessage
odeimaiz Mar 21, 2025
9cd1a0c
optOutMessage
odeimaiz Mar 21, 2025
43c5e89
eyes next to the fields
odeimaiz Mar 21, 2025
12264dd
dynamic eyes
odeimaiz Mar 21, 2025
b048916
minor
odeimaiz Mar 21, 2025
27c13c8
username is nullable
odeimaiz Mar 21, 2025
c11d3a0
minor
odeimaiz Mar 21, 2025
f138b4c
missing fields
pcrespov Mar 21, 2025
3a30f39
constant
pcrespov Mar 21, 2025
4aa012a
refactor
pcrespov Mar 21, 2025
86422c2
test
pcrespov Mar 21, 2025
38400c1
retire put
pcrespov Mar 21, 2025
8ea9dfd
update OAS
pcrespov Mar 21, 2025
4c3f5be
services/webserver api version: 0.61.3 → 0.61.4
pcrespov Mar 21, 2025
e37dc5f
wrong import
pcrespov Mar 21, 2025
a0c3bb2
wrong example
pcrespov Mar 21, 2025
f07de3f
fixes test
pcrespov Mar 21, 2025
12d0665
Merge branch 'master' into feature/hide-username
odeimaiz Mar 24, 2025
f16bd1d
Merge branch 'is38/missing-username-privacy' of github.com:pcrespov/o…
odeimaiz Mar 24, 2025
e9735c4
minor
odeimaiz Mar 24, 2025
6fca955
more startup calls
odeimaiz Mar 24, 2025
03a15c3
missing fields
pcrespov Mar 21, 2025
b67d799
constant
pcrespov Mar 21, 2025
3c17c2b
refactor
pcrespov Mar 21, 2025
fd4dad6
test
pcrespov Mar 21, 2025
a205478
retire put
pcrespov Mar 21, 2025
00eaff1
update OAS
pcrespov Mar 21, 2025
5e0789f
services/webserver api version: 0.61.3 → 0.61.4
pcrespov Mar 21, 2025
06e7f4c
wrong import
pcrespov Mar 21, 2025
eaa357e
wrong example
pcrespov Mar 21, 2025
3de5949
fixes test
pcrespov Mar 21, 2025
ea59980
@odeimaiz review: missing field
pcrespov Mar 24, 2025
1e0eaff
better practices
odeimaiz Mar 24, 2025
d63516b
minor
odeimaiz Mar 24, 2025
f0f4e0f
enable button
odeimaiz Mar 24, 2025
f5107dc
profileFields
odeimaiz Mar 24, 2025
4866179
Merge branch 'is38/missing-username-privacy' of github.com:pcrespov/o…
odeimaiz Mar 24, 2025
6ea4ba7
minor
odeimaiz Mar 24, 2025
66e3490
do not check tasks
odeimaiz Mar 24, 2025
fd65ac4
Merge branch 'master' into feature/hide-username
odeimaiz Mar 24, 2025
10f741d
bad merge
odeimaiz Mar 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ qx.Class.define("osparc.data.model.User", {

const userId = ("id" in userData) ? parseInt(userData["id"]) : parseInt(userData["userId"]);
const groupId = ("gid" in userData) ? parseInt(userData["gid"]) : parseInt(userData["groupId"]);
const username = userData["userName"];
const username = userData["userName"] || "-";
const email = ("login" in userData) ? userData["login"] : userData["email"];
let firstName = "";
if (userData["first_name"]) {
Expand Down Expand Up @@ -60,7 +60,7 @@ qx.Class.define("osparc.data.model.User", {
lastName,
email,
thumbnail,
label: username,
label: userData["userName"] || description,
description,
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
appearance: "form-button-outlined",
label: this.tr("App Mode"),
toolTipText: this.tr("Start App Mode"),
icon: "@FontAwesome5Solid/play/14",
icon: osparc.dashboard.CardBase.MODE_APP,
marginRight: 10,
marginTop: 7,
...osparc.navigation.NavigationBar.BUTTON_OPTIONS
Expand Down Expand Up @@ -837,7 +837,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", {

const startAppBtn = this.__startAppButton = new qx.ui.form.Button().set({
label: this.tr("Start"),
icon: "@FontAwesome5Solid/play/14",
icon: osparc.dashboard.CardBase.MODE_APP,
toolTipText: this.tr("Start App Mode"),
height: buttonsHeight
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {

this._setLayout(new qx.ui.layout.VBox(15));

this.__userProfileData = {};
this.__userPrivacyData = {};

this.__fetchProfile();

this._add(this.__createProfileUser());
Expand All @@ -45,8 +48,11 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
members: {
__userProfileData: null,
__userProfileModel: null,
__userProfileRenderer: null,
__updateProfileBtn: null,
__userPrivacyData: null,
__userPrivacyModel: null,
__updatePrivacyBtn: null,
__userProfileForm: null,

__fetchProfile: function() {
Expand All @@ -69,16 +75,29 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
"expirationDate": data["expirationDate"] || null,
});
}
this.__updateProfileBtn.setEnabled(false);
},

__setDataToPrivacy: function(privacyData) {
if (privacyData) {
this.__userPrivacyData = privacyData;
this.__userPrivacyModel.set({
"hideUsername": "hideUsername" in privacyData ? privacyData["hideUsername"] : false,
"hideFullname": "hideFullname" in privacyData ? privacyData["hideFullname"] : true,
"hideEmail": "hideEmail" in privacyData ? privacyData["hideEmail"] : true,
});

const visibleIcon = "@FontAwesome5Solid/eye/12";
const hiddenIcon = "@FontAwesome5Solid/eye-slash/12";
const icons = {
0: this.__userPrivacyModel.getHideUsername() ? hiddenIcon : visibleIcon,
1: this.__userPrivacyModel.getHideFullname() ? hiddenIcon : visibleIcon,
2: this.__userPrivacyModel.getHideFullname() ? hiddenIcon : visibleIcon,
3: this.__userPrivacyModel.getHideEmail() ? hiddenIcon : visibleIcon,
};
this.__userProfileRenderer.setIcons(icons);
}
this.__updatePrivacyBtn.setEnabled(false);
},

__createProfileUser: function() {
Expand All @@ -105,12 +124,13 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
readOnly: true
});

const form = this.__userProfileForm = new qx.ui.form.Form();
form.add(username, "Username", null, "username");
form.add(firstName, "First Name", null, "firstName");
form.add(lastName, "Last Name", null, "lastName");
form.add(email, "Email", null, "email");
box.add(new qx.ui.form.renderer.Single(form));
const profileForm = this.__userProfileForm = new qx.ui.form.Form();
profileForm.add(username, "Username", null, "username");
profileForm.add(firstName, "First Name", null, "firstName");
profileForm.add(lastName, "Last Name", null, "lastName");
profileForm.add(email, "Email", null, "email");
const singleWithIcon = this.__userProfileRenderer = new osparc.ui.form.renderer.SingleWithIcon(profileForm);
box.add(singleWithIcon);

const expirationLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)).set({
paddingLeft: 16,
Expand Down Expand Up @@ -167,21 +187,23 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
namesValidator.add(firstName, qx.util.Validate.regExp(/[^\.\d]+/), this.tr("Avoid dots or numbers in text"));
namesValidator.add(lastName, qx.util.Validate.regExp(/^$|[^\.\d]+/), this.tr("Avoid dots or numbers in text")); // allow also empty last name

const updateBtn = new qx.ui.form.Button("Update Profile").set({
const updateProfileBtn = this.__updateProfileBtn = new qx.ui.form.Button().set({
label: this.tr("Update Profile"),
appearance: "form-button",
alignX: "right",
allowGrowX: false
allowGrowX: false,
enabled: false,
});
box.add(updateBtn);
box.add(updateProfileBtn);

updateBtn.addListener("execute", () => {
updateProfileBtn.addListener("execute", () => {
if (!osparc.data.Permissions.getInstance().canDo("user.user.update", true)) {
this.__resetUserData();
return;
}

const patchData = {};
if (this.__userProfileData["username"] !== model.getUsername()) {
if (this.__userProfileData["userName"] !== model.getUsername()) {
patchData["userName"] = model.getUsername();
}
if (this.__userProfileData["first_name"] !== model.getFirstName()) {
Expand Down Expand Up @@ -211,61 +233,88 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
}
});

const profileFields = [
username,
firstName,
lastName,
]
const valueChanged = () => {
const anyChanged =
username.getValue() !== this.__userProfileData["userName"] ||
firstName.getValue() !== this.__userProfileData["first_name"] ||
lastName.getValue() !== this.__userProfileData["last_name"];
updateProfileBtn.setEnabled(anyChanged);
};
valueChanged();
profileFields.forEach(privacyField => privacyField.addListener("changeValue", () => valueChanged()));

return box;
},

__createPrivacySection: function() {
// binding to a model
const defaultModel = {
"hideUsername": false,
"hideFullname": true,
"hideEmail": true,
};

const privacyModel = this.__userPrivacyModel = qx.data.marshal.Json.createModel(defaultModel, true);

const box = osparc.ui.window.TabbedView.createSectionBox(this.tr("Privacy"));
box.set({
alignX: "left",
maxWidth: 500
});

const label = osparc.ui.window.TabbedView.createHelpLabel(this.tr("For Privacy reasons, you might want to hide your First and Last Names and/or the Email to other users"));
const label = osparc.ui.window.TabbedView.createHelpLabel(this.tr("For Privacy reasons, you might want to hide some personal data."));
box.add(label);

const hideUsername = new qx.ui.form.CheckBox().set({
value: defaultModel.hideUsername
});
const hideFullname = new qx.ui.form.CheckBox().set({
value: true
value: defaultModel.hideFullname
});
const hideEmail = new qx.ui.form.CheckBox().set({
value: true
value: defaultModel.hideEmail
});

const form = new qx.ui.form.Form();
form.add(hideFullname, "Hide Full Name", null, "hideFullname");
form.add(hideEmail, "Hide Email", null, "hideEmail");
box.add(new qx.ui.form.renderer.Single(form));

// binding to a model
const raw = {
"hideFullname": true,
"hideEmail": true,
};
const privacyForm = new qx.ui.form.Form();
privacyForm.add(hideUsername, "Hide Username", null, "hideUsername");
privacyForm.add(hideFullname, "Hide Full Name", null, "hideFullname");
privacyForm.add(hideEmail, "Hide Email", null, "hideEmail");
box.add(new qx.ui.form.renderer.Single(privacyForm));

const model = this.__userPrivacyModel = qx.data.marshal.Json.createModel(raw);
const controller = new qx.data.controller.Object(model);
controller.addTarget(hideFullname, "value", "hideFullname", true);
controller.addTarget(hideEmail, "value", "hideEmail", true);
const privacyModelCtrl = new qx.data.controller.Object(privacyModel);
privacyModelCtrl.addTarget(hideUsername, "value", "hideUsername", true);
privacyModelCtrl.addTarget(hideFullname, "value", "hideFullname", true);
privacyModelCtrl.addTarget(hideEmail, "value", "hideEmail", true);

const privacyBtn = new qx.ui.form.Button("Update Privacy").set({
const updatePrivacyBtn = this.__updatePrivacyBtn = new qx.ui.form.Button().set({
label: this.tr("Update Privacy"),
appearance: "form-button",
alignX: "right",
allowGrowX: false
allowGrowX: false,
enabled: false,
});
box.add(privacyBtn);
privacyBtn.addListener("execute", () => {
box.add(updatePrivacyBtn);
updatePrivacyBtn.addListener("execute", () => {
if (!osparc.data.Permissions.getInstance().canDo("user.user.update", true)) {
this.__resetPrivacyData();
return;
}
const patchData = {
"privacy": {}
};
if (this.__userPrivacyData["hideFullname"] !== model.getHideFullname()) {
patchData["privacy"]["hideFullname"] = model.getHideFullname();
if (this.__userPrivacyData["hideUsername"] !== privacyModel.getHideUsername()) {
patchData["privacy"]["hideUsername"] = privacyModel.getHideUsername();
}
if (this.__userPrivacyData["hideFullname"] !== privacyModel.getHideFullname()) {
patchData["privacy"]["hideFullname"] = privacyModel.getHideFullname();
}
if (this.__userPrivacyData["hideEmail"] !== model.getHideEmail()) {
patchData["privacy"]["hideEmail"] = model.getHideEmail();
if (this.__userPrivacyData["hideEmail"] !== privacyModel.getHideEmail()) {
patchData["privacy"]["hideEmail"] = privacyModel.getHideEmail();
}

if (
Expand Down Expand Up @@ -298,6 +347,36 @@ qx.Class.define("osparc.desktop.account.ProfilePage", {
}
});

const optOutMessage = new qx.ui.basic.Atom().set({
label: this.tr("If all searchable fields are hidden, you will not be findable."),
icon: "@FontAwesome5Solid/exclamation-triangle/14",
gap: 8,
allowGrowX: false,
});
optOutMessage.getChildControl("icon").setTextColor("warning-yellow")
box.add(optOutMessage);

const privacyFields = [
hideUsername,
hideFullname,
hideEmail,
]
const valueChanged = () => {
const anyChanged =
hideUsername.getValue() !== this.__userPrivacyData["hideUsername"] ||
hideFullname.getValue() !== this.__userPrivacyData["hideFullname"] ||
hideEmail.getValue() !== this.__userPrivacyData["hideEmail"];
updatePrivacyBtn.setEnabled(anyChanged);

if (privacyFields.every(privacyField => privacyField.getValue())) {
optOutMessage.show();
} else {
optOutMessage.exclude();
}
};
valueChanged();
privacyFields.forEach(privacyField => privacyField.addListener("changeValue", () => valueChanged()));

return box;
},

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* ************************************************************************

osparc - the simcore frontend

https://osparc.io

Copyright:
2025 IT'IS Foundation, https://itis.swiss

License:
MIT: https://opensource.org/licenses/MIT

Authors:
* Odei Maiz (odeimaiz)

************************************************************************ */

qx.Class.define("osparc.ui.form.renderer.SingleWithIcon", {
extend: qx.ui.form.renderer.Single,

construct: function(form, icons) {
if (icons) {
this.__icons = icons;
} else {
this.__icons = {};
}

this.base(arguments, form);
},

members: {
__icons: null,

setIcons: function(icons) {
this.__icons = icons;

this._onFormChange();
},

// overridden
addItems: function(items, names, title, itemOptions, headerOptions) {
this.base(arguments, items, names, title, itemOptions, headerOptions);

// header
let row = title === null ? 0 : 1;

for (let i = 0; i < items.length; i++) {
if (i in this.__icons) {
const image = new qx.ui.basic.Image(this.__icons[i]).set({
alignY: "middle",
});
this._add(image, {
row,
column: 2,
});
}

row++;
}
},
}
});
Loading
Loading