Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
161e46c
"Failed" -> "Unsuccessful"
odeimaiz Mar 13, 2025
58a7959
Failed -> Unsuccessful
odeimaiz Mar 13, 2025
5950f65
@pcrespov messages-guidelines.md
odeimaiz Mar 13, 2025
7fd3fb3
minor
odeimaiz Mar 13, 2025
8b27500
Unsuccessful Login
odeimaiz Mar 13, 2025
566187d
a lot of replacing
odeimaiz Mar 13, 2025
755019e
pass error to logError
odeimaiz Mar 13, 2025
29a5e1b
Merge branch 'master' into dont-say-fail
odeimaiz Mar 14, 2025
577e60d
move console.error
odeimaiz Mar 14, 2025
94d1a8f
getMessage
odeimaiz Mar 14, 2025
6186177
oopsi
odeimaiz Mar 14, 2025
87a94fb
extractMessage
odeimaiz Mar 14, 2025
87f9c4e
Merge branch 'master' into dont-say-fail
odeimaiz Mar 14, 2025
df7a040
minors
odeimaiz Mar 14, 2025
4a95aaf
Merge branch 'dont-say-fail' of github.com:odeimaiz/osparc-simcore in…
odeimaiz Mar 14, 2025
091ad0a
Update services/static-webserver/client/source/class/osparc/auth/ui/L…
odeimaiz Mar 14, 2025
384a0c8
Update services/static-webserver/client/source/class/osparc/file/File…
odeimaiz Mar 14, 2025
96ed2e5
Update services/static-webserver/client/source/class/osparc/desktop/p…
odeimaiz Mar 14, 2025
5511b20
Update services/static-webserver/client/source/class/osparc/file/File…
odeimaiz Mar 14, 2025
3717274
Update services/static-webserver/client/source/class/osparc/info/Merg…
odeimaiz Mar 14, 2025
a39947c
Update services/static-webserver/client/source/class/osparc/study/Stu…
odeimaiz Mar 14, 2025
2964ed0
Update services/static-webserver/client/source/class/osparc/info/Serv…
odeimaiz Mar 14, 2025
e308e39
Update services/static-webserver/client/source/class/osparc/info/Stud…
odeimaiz Mar 14, 2025
0b2af5f
Update services/static-webserver/client/source/class/osparc/desktop/o…
odeimaiz Mar 14, 2025
c93f0f7
Something went wrong while
odeimaiz Mar 14, 2025
1afd64c
There was an error
odeimaiz Mar 14, 2025
742c9b7
update startupCalls test
odeimaiz Mar 14, 2025
e38e7d3
minor
odeimaiz Mar 14, 2025
cb288df
remove waitForNetworkIdle
odeimaiz Mar 14, 2025
cf94293
Update startupCalls.test.js
odeimaiz Mar 14, 2025
5998844
Update startupCalls.test.js
odeimaiz Mar 14, 2025
bc24d09
Merge branch 'master' into dont-say-fail
odeimaiz Mar 14, 2025
4758d16
maxWorkers
odeimaiz Mar 15, 2025
e2ca9a7
maxWorkers
odeimaiz Mar 15, 2025
ab1a307
maxConcurrency
odeimaiz Mar 16, 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
134 changes: 134 additions & 0 deletions docs/messages-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Error and Warning Message Guidelines

These guidelines ensure that messages are user-friendly, clear, and helpful while maintaining a professional tone. 🚀

Some details:

- Originated from [guidelines](https://wiki.speag.com/projects/SuperMash/wiki/Concepts/GUI) by @eofli and refined iterating with AI
- Here’s the fully expanded and rewritten list of **error and warning message guidelines**, each with:
- A **guideline**
- A **rationale**
- A ❌ **bad example**
- A ✅ **good example**
- A **reference**
- This list is intended to be short enough to be read and understood for humans as well as complete so that it can be used as context for automatic correction of error/warning messages

---

## 1. Be Clear and Concise

- **Guideline:** Use straightforward language to describe the issue without unnecessary words.
- **Rationale:** Users can quickly understand the problem and take corrective action when messages are simple and to the point.
- ❌ **Bad Example:**
`"An error has occurred due to an unexpected input that couldn't be parsed correctly."`
- ✅ **Good Example:**
`"We couldn't process your request. Please check your input and try again."`
- **[Reference](https://uxwritinghub.com/error-message-examples/)**

---

## 2. Provide Specific and Actionable Information

- **Guideline:** Clearly state what went wrong and how the user can fix it.
- **Rationale:** Specific guidance helps users resolve issues efficiently, reducing frustration.
- ❌ **Bad Example:**
`"Something went wrong."`
- ✅ **Good Example:**
`"Your session has expired. Please log in again to continue."`
- **[Reference](https://www.nngroup.com/articles/error-message-guidelines/)**

---

## 3. Avoid Technical Jargon

- **Guideline:** Use plain language instead of technical terms or codes.
- **Rationale:** Non-technical users may not understand complex terminology, hindering their ability to resolve the issue.
- ❌ **Bad Example:**
`"Error 429: Too many requests per second."`
- ✅ **Good Example:**
`"You’ve made too many requests. Please wait a moment and try again."`
- **[Reference](https://cxl.com/blog/error-messages/)**

---

## 4. Use a Polite and Non-Blaming Tone

- **Guideline:** Frame messages in a way that doesn't place blame on the user.
- **Rationale:** A respectful tone maintains a positive user experience and encourages users to continue using the application.
- ❌ **Bad Example:**
`"You entered the wrong password."`
- ✅ **Good Example:**
`"The password doesn't match. Please try again."`
- **[Reference](https://atlassian.design/content/writing-guidelines/writing-error-messages/)**

---

## 5. Avoid Negative Words and Phrases

- **Guideline:** Steer clear of words like "error," "failed," "invalid," or "illegal."
- **Rationale:** Positive language reduces user anxiety and creates a more supportive experience.
- ❌ **Bad Example:**
`"Invalid email address."`
- ✅ **Good Example:**
`"The email address format doesn't look correct. Please check and try again."`
- **[Reference](https://atlassian.design/content/writing-guidelines/writing-error-messages/)**

---

## 6. Place Messages Appropriately

- **Guideline:** Display error messages near the relevant input field or in a clear, noticeable location.
- **Rationale:** Proper placement ensures users notice the message and understand where the issue occurred.
- ❌ **Bad Example:**
Showing a generic "Form submission failed" message at the top of the page.
- ✅ **Good Example:**
Placing "Please enter a valid phone number" directly below the phone input field.
- **[Reference](https://www.smashingmagazine.com/2022/08/error-messages-ux-design/)**

---

## 7. Use Inline Validation When Possible

- **Guideline:** Provide real-time feedback as users interact with input fields.
- **Rationale:** Inline validation allows users to correct errors immediately, enhancing the flow and efficiency of the interaction.
- ❌ **Bad Example:**
Waiting until form submission to show all validation errors.
- ✅ **Good Example:**
Displaying "Password must be at least 8 characters" while the user types.
- **[Reference](https://cxl.com/blog/error-messages/)**

---

## 8. Avoid Using All-Caps and Excessive Punctuation

- **Guideline:** Refrain from writing messages in all capital letters or using multiple exclamation marks.
- **Rationale:** All-caps and excessive punctuation can be perceived as shouting, which may frustrate users.
- ❌ **Bad Example:**
`"INVALID INPUT!!!"`
- ✅ **Good Example:**
`"This input doesn't look correct. Please check and try again."`
- **[Reference](https://uxwritinghub.com/error-message-examples/)**

---

## 9. Use Humor Sparingly

- **Guideline:** Incorporate light-hearted language only when appropriate and aligned with the application's tone.
- **Rationale:** While humor can ease tension, it may not be suitable for all users or situations and can sometimes be misinterpreted.
- ❌ **Bad Example:**
`"Oopsie daisy! You broke something!"`
- ✅ **Good Example:**
`"Something went wrong. Try again, or contact support if the issue continues."`
- **[Reference](https://cxl.com/blog/error-messages/)**

---

## 10. Offer Alternative Solutions or Support

- **Guideline:** If the user cannot resolve the issue independently, provide a way to contact support or access help resources.
- **Rationale:** Offering support options ensures users don't feel stranded and can seek help to resolve their issues.
- ❌ **Bad Example:**
`"Access denied."`
- ✅ **Good Example:**
`"You don't have permission to view this page. Contact support if you think this is a mistake."`
- **[Reference](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-error-handling-guidelines/)**
Original file line number Diff line number Diff line change
Expand Up @@ -460,15 +460,15 @@ qx.Class.define("osparc.Application", {

if (osparc.auth.Data.getInstance().isGuest()) {
const msg = osparc.utils.Utils.createAccountMessage();
osparc.FlashMessenger.getInstance().logAs(msg, "WARNING");
osparc.FlashMessenger.logAs(msg, "WARNING");
} else if (profile["expirationDate"]) {
const now = new Date();
const today = new Date(now.toISOString().slice(0, 10));
const expirationDay = new Date(profile["expirationDate"]);
const daysToExpiration = osparc.utils.Utils.daysBetween(today, expirationDay);
if (daysToExpiration < 7) {
const msg = osparc.utils.Utils.expirationMessage(daysToExpiration);
osparc.FlashMessenger.getInstance().logAs(msg, "WARNING");
osparc.FlashMessenger.logAs(msg, "WARNING");
}
}

Expand Down Expand Up @@ -561,9 +561,9 @@ qx.Class.define("osparc.Application", {

__loggedOut: function(forcedReason) {
if (forcedReason) {
osparc.FlashMessenger.getInstance().logAs(forcedReason, "WARNING", 0);
osparc.FlashMessenger.logAs(forcedReason, "WARNING", 0);
} else {
osparc.FlashMessenger.getInstance().logAs(this.tr("You are logged out"), "INFO");
osparc.FlashMessenger.logAs(this.tr("You are logged out"), "INFO");
}
this.__closeAllAndToLoginPage();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ qx.Class.define("osparc.CookieExpirationTracker", {
const timeout = osparc.utils.Utils.formatSeconds(timeoutSec);
const text = qx.locale.Manager.tr(`Your session will expire in ${timeout}.<br>Please log out and log in again.`);
if (this.__message === null) {
this.__message = osparc.FlashMessenger.getInstance().logAs(text, "WARNING", timeoutSec*1000);
this.__message = osparc.FlashMessenger.logAs(text, "WARNING", timeoutSec*1000);
this.__message.getChildControl("closebutton").exclude();
} else {
this.__message.setMessage(text);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* Here is a little example of how to use the class.
*
* <pre class='javascript'>
* osparc.FlashMessenger.getInstance().logAs(log);
* osparc.FlashMessenger.logAs(log);
* </pre>
*/

Expand All @@ -55,9 +55,33 @@ qx.Class.define("osparc.FlashMessenger", {

statics: {
MAX_DISPLAYED: 3,

extractMessage: function(input, defaultMessage = "") {
if (input) {
if (typeof input === "string") {
return input;
} else if (osparc.utils.Utils.isObject(input) && "message" in input) {
if (typeof input["message"] === "string") {
return input["message"];
} else if (osparc.utils.Utils.isObject(input["message"]) && "message" in input["message"] && typeof input["message"]["message"] === "string") {
return input["message"]["message"];
}
}
}
return defaultMessage;
},

logAs: function(message, level, duration) {
return this.getInstance().logAs(message, level, duration);
}
},

logError: function(error, defaultMessage = qx.locale.Manager.tr("Oops, something went wrong"), duration = null) {
if (error) {
console.error(error);
}
const msg = this.extractMessage(error, defaultMessage);
return this.getInstance().logAs(msg, "ERROR", duration);
},
},

members: {
Expand All @@ -68,7 +92,7 @@ qx.Class.define("osparc.FlashMessenger", {
/**
* Public function to log a FlashMessage to the user.
*
* @param {String} message Message that the message will show.
* @param {String || Object} message Message (or Object containing the message) that the message will show.
* @param {String="INFO","DEBUG","WARNING","ERROR"} level Level of the warning. The color of the badge will change accordingly.
* @param {Number} duration
*/
Expand All @@ -81,9 +105,7 @@ qx.Class.define("osparc.FlashMessenger", {
},

log: function(logMessage) {
const message = osparc.utils.Utils.isObject(logMessage.message) && "message" in logMessage.message ?
logMessage.message.message :
logMessage.message;
const message = this.self().extractMessage(logMessage);

const level = logMessage.level.toUpperCase(); // "DEBUG", "INFO", "WARNING", "ERROR"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ qx.Class.define("osparc.NewUITracker", {
msg += "<br>";
msg += qx.locale.Manager.tr("Click the Reload button to get the latest features.");
// permanent message
const flashMessage = osparc.FlashMessenger.getInstance().logAs(msg, "INFO", 0).set({
const flashMessage = osparc.FlashMessenger.logAs(msg, "INFO", 0).set({
maxWidth: 500
});
const reloadButton = osparc.utils.Utils.reloadNoCacheButton();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ qx.Class.define("osparc.Preferences", {
osparc.Preferences.patchPreference(preferenceId, newValue)
.then(() => preferencesSettings.set(preferenceId, newValue))
.catch(err => {
console.error(err);
osparc.FlashMessenger.logAs(err.message, "ERROR");
osparc.FlashMessenger.logError(err);
preferenceField.setValue(oldValue);
})
.finally(() => preferenceField.setEnabled(true));
Expand Down Expand Up @@ -208,10 +207,7 @@ qx.Class.define("osparc.Preferences", {
wallets.forEach(wallet => wallet.setPreferredWallet(wallet.getWalletId() === walletId));
this.setPreferredWalletId(walletId);
})
.catch(err => {
console.error(err);
osparc.FlashMessenger.logAs(err.message, "ERROR");
});
.catch(err => osparc.FlashMessenger.logError(err));
},

__patchPreference: function(value, _, propName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,15 @@ qx.Class.define("osparc.activityManager.ActivityManager", {
/*
const runButton = new qx.ui.toolbar.Button(this.tr("Run"), "@FontAwesome5Solid/play/14");
actionsPart.add(runButton);
runButton.addListener("execute", () => osparc.FlashMessenger.getInstance().logAs("Not implemented"));
runButton.addListener("execute", () => osparc.FlashMessenger.logAs("Not implemented"));

const stopButton = new qx.ui.toolbar.Button(this.tr("Stop"), "@FontAwesome5Solid/stop-circle/14");
actionsPart.add(stopButton);
stopButton.addListener("execute", () => osparc.FlashMessenger.getInstance().logAs("Not implemented"));
stopButton.addListener("execute", () => osparc.FlashMessenger.logAs("Not implemented"));

const infoButton = new qx.ui.toolbar.Button(this.tr("Info"), "@FontAwesome5Solid/info/14");
actionsPart.add(infoButton);
infoButton.addListener("execute", () => osparc.FlashMessenger.getInstance().logAs("Not implemented"));
infoButton.addListener("execute", () => osparc.FlashMessenger.logAs("Not implemented"));

[runButton, stopButton, infoButton].map(button => this.__tree.bind("selected", button, "enabled", {
converter: data => data.length > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,7 @@ qx.Class.define("osparc.activityManager.ActivityTree", {
}
this.fireEvent("treeUpdated");
})
.catch(e => {
console.error(e);
})
.catch(err => console.error(err))
.then(() => {
// Give a 2 seconds delay
setTimeout(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ qx.Class.define("osparc.admin.Announcements", {
}
if (widgets.length === 0) {
const msg = "Select at least one widget";
osparc.FlashMessenger.getInstance().logAs(msg, "WARNING");
osparc.FlashMessenger.logAs(msg, "WARNING");
}
const announcementData = {
"id": osparc.utils.Utils.uuidV4(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ qx.Class.define("osparc.auth.Data", {

properties: {
/**
* Basic authentification with a token
* Basic authentication with a token
*/
auth: {
init: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ qx.Class.define("osparc.auth.LoginWithDecorators", {
pages.setSelection([resetPassword]);
}
} else if (urlFragment.params && urlFragment.params.registered) {
osparc.FlashMessenger.getInstance().logAs(this.tr("Your account has been created.<br>You can now use your credentials to login."));
osparc.FlashMessenger.logAs(this.tr("Your account has been created.<br>You can now use your credentials to login."));
}

login.addListener("toRegister", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,13 @@ qx.Class.define("osparc.auth.Manager", {
} else {
const resp = JSON.parse(xhr.responseText);
if (resp.error == null) {
reject(this.tr("Login failed"));
reject(this.tr("Unsuccessful Login"));
} else {
reject(resp.error.message);
}
}
};
xhr.onerror = () => reject(this.tr("Login failed"));
xhr.onerror = () => reject(this.tr("Unsuccessful Login"));
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(params));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ qx.Class.define("osparc.auth.ui.Login2FAValidationCodeView", {
});
this.restartSMSButton(retryAfter);
})
.catch(err => osparc.FlashMessenger.logAs(err.message, "ERROR"))
.catch(err => osparc.FlashMessenger.logError(err))
.finally(() => resendCodeSMSBtn.setFetching(false));
}, this);

Expand All @@ -145,7 +145,7 @@ qx.Class.define("osparc.auth.ui.Login2FAValidationCodeView", {
});
this.restartEmailButton(retryAfter);
})
.catch(err => osparc.FlashMessenger.logAs(err.message, "ERROR"))
.catch(err => osparc.FlashMessenger.logError(err))
.finally(() => resendCodeEmailBtn.setFetching(false));
}, this);
this.add(resendLayout);
Expand Down Expand Up @@ -188,7 +188,7 @@ qx.Class.define("osparc.auth.ui.Login2FAValidationCodeView", {
msg = String(msg) || this.tr("Invalid code");
validationCodeTF.setInvalidMessage(msg);

osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
osparc.FlashMessenger.logError(msg);
};

if (this._form.validate()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ qx.Class.define("osparc.auth.ui.LoginView", {

const twoFactorAuthCbk = (nextStep, message, retryAfter) => {
this.__loginBtn.setFetching(false);
osparc.FlashMessenger.getInstance().logAs(message, "INFO");
osparc.FlashMessenger.logAs(message, "INFO");
this.fireDataEvent("to2FAValidationCode", {
userEmail: email.getValue(),
nextStep,
Expand All @@ -251,15 +251,15 @@ qx.Class.define("osparc.auth.ui.LoginView", {
const failFun = msg => {
this.__loginBtn.setFetching(false);
// TODO: can get field info from response here
msg = String(msg) || this.tr("Typed an invalid email or password");
msg = String(msg) || this.tr("Invalid email or password");
[email, pass].forEach(item => {
item.set({
invalidMessage: msg,
valid: false
});
});

osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
osparc.FlashMessenger.logError(msg);
};

const manager = osparc.auth.Manager.getInstance();
Expand Down
Loading
Loading