diff --git a/packages/service-library/src/servicelib/aiohttp/rest_middlewares.py b/packages/service-library/src/servicelib/aiohttp/rest_middlewares.py index 0fc11dda7a2a..4847389b7195 100644 --- a/packages/service-library/src/servicelib/aiohttp/rest_middlewares.py +++ b/packages/service-library/src/servicelib/aiohttp/rest_middlewares.py @@ -24,10 +24,9 @@ from .typing_extension import Handler, Middleware DEFAULT_API_VERSION = "v0" -_FMSG_INTERNAL_ERROR_USER_FRIENDLY_WITH_OEC = ( +_FMSG_INTERNAL_ERROR_USER_FRIENDLY = ( "We apologize for the inconvenience. " - "Our team has recorded the issue [SupportID={error_code}]. " - "If the issue persists please report it." + "The issue has been recorded, please report it if it persists." ) @@ -52,7 +51,7 @@ def _process_and_raise_unexpected_error(request: web.BaseRequest, err: Exception "request.path": f"{request.path}", } - user_error_msg = _FMSG_INTERNAL_ERROR_USER_FRIENDLY_WITH_OEC.format( + user_error_msg = _FMSG_INTERNAL_ERROR_USER_FRIENDLY.format( error_code=error_code ) http_error = create_http_error( diff --git a/packages/service-library/tests/aiohttp/test_rest_middlewares.py b/packages/service-library/tests/aiohttp/test_rest_middlewares.py index 0adfa6cf80dc..0bd501066d9b 100644 --- a/packages/service-library/tests/aiohttp/test_rest_middlewares.py +++ b/packages/service-library/tests/aiohttp/test_rest_middlewares.py @@ -13,11 +13,9 @@ import pytest from aiohttp import web from aiohttp.test_utils import TestClient -from common_library.error_codes import parse_error_codes from common_library.json_serialization import json_dumps from servicelib.aiohttp import status from servicelib.aiohttp.rest_middlewares import ( - _FMSG_INTERNAL_ERROR_USER_FRIENDLY_WITH_OEC, envelope_middleware_factory, error_middleware_factory, ) @@ -234,14 +232,6 @@ async def test_raised_unhandled_exception( assert not data assert error - # user friendly message with OEC reference - assert "OEC" in error["message"] - parsed_oec = parse_error_codes(error["message"]).pop() - assert ( - _FMSG_INTERNAL_ERROR_USER_FRIENDLY_WITH_OEC.format(error_code=parsed_oec) - == error["message"] - ) - # avoids details assert not error.get("errors") assert not error.get("logs") diff --git a/services/static-webserver/client/source/class/osparc/FlashMessenger.js b/services/static-webserver/client/source/class/osparc/FlashMessenger.js index 1067c4f65700..d9c886fd5066 100644 --- a/services/static-webserver/client/source/class/osparc/FlashMessenger.js +++ b/services/static-webserver/client/source/class/osparc/FlashMessenger.js @@ -86,7 +86,35 @@ qx.Class.define("osparc.FlashMessenger", { console.error(error); } const msg = this.extractMessage(error, defaultMessage); - return this.getInstance().logAs(msg, "ERROR", duration); + const flashMessage = this.getInstance().logAs(msg, "ERROR", duration); + if (error && error["supportId"]) { + flashMessage.addWidget(this.__createCopyOECWidget(msg, error["supportId"])); + flashMessage.setDuration(flashMessage.getDuration()*2); + } + return flashMessage; + }, + + __createCopyOECWidget: function(message, supportId) { + const errorLabel = new qx.ui.basic.Atom().set({ + label: supportId, + icon: "@FontAwesome5Solid/copy/10", + iconPosition: "right", + gap: 8, + cursor: "pointer", + alignX: "center", + allowGrowX: false, + }); + errorLabel.addListener("tap", () => { + const dataToClipboard = { + message, + supportId, + timestamp: new Date().toString(), + url: window.location.href, + studyId: osparc.store.Store.getInstance().getCurrentStudy() || "", + } + osparc.utils.Utils.copyTextToClipboard(JSON.stringify(dataToClipboard)); + }); + return errorLabel; }, }, @@ -143,14 +171,9 @@ qx.Class.define("osparc.FlashMessenger", { } this.__displayedMessagesCount++; - let duration = flashMessage.getDuration(); - if (duration === null) { - const message = flashMessage.getMessage(); - const wordCount = message.split(" ").length; - duration = Math.max(5500, wordCount*500); // An average reader takes 300ms to read a word - } + const duration = flashMessage.getDuration(); if (duration !== 0) { - qx.event.Timer.once(() => this.removeMessage(flashMessage), this, duration); + flashMessage.timer = setTimeout(() => this.removeMessage(flashMessage), duration); } }, diff --git a/services/static-webserver/client/source/class/osparc/ui/message/FlashMessage.js b/services/static-webserver/client/source/class/osparc/ui/message/FlashMessage.js index d111984ad1e7..c7a8f811dfec 100644 --- a/services/static-webserver/client/source/class/osparc/ui/message/FlashMessage.js +++ b/services/static-webserver/client/source/class/osparc/ui/message/FlashMessage.js @@ -31,7 +31,7 @@ qx.Class.define("osparc.ui.message.FlashMessage", { */ construct: function(message, level, duration) { this.base(arguments); - this._setLayout(new qx.ui.layout.VBox(15)); + this._setLayout(new qx.ui.layout.VBox(10)); this.set({ padding: 18, @@ -48,14 +48,13 @@ qx.Class.define("osparc.ui.message.FlashMessage", { textColor: this.self().LOG_LEVEL_COLOR_MAP[level].color }); - if (message) { - this.setMessage(message); - } + this.setMessage(message); - // also support duration 0: the message won't be automatically removed - if (duration != null) { - this.setDuration(duration); + if ([null, undefined].includes(duration)) { + const wordCount = message.split(" ").length; + duration = Math.max(5500, wordCount*500); // An average reader takes 300ms to read a word } + this.setDuration(duration); this.getChildControl("closebutton"); }, @@ -68,13 +67,14 @@ qx.Class.define("osparc.ui.message.FlashMessage", { message: { check: "String", - nullable: true, - apply: "__applyMessage" + nullable: false, + apply: "__applyMessage", }, duration: { check: "Number", - nullable: true + init: null, + nullable: true, } }, @@ -142,10 +142,7 @@ qx.Class.define("osparc.ui.message.FlashMessage", { }, __applyMessage: function(value) { - const label = this.getChildControl("message"); - if (label) { - label.setValue(value); - } + this.getChildControl("message").setValue(value); }, addWidget: function(widget) { diff --git a/services/web/server/src/simcore_service_webserver/login/_constants.py b/services/web/server/src/simcore_service_webserver/login/_constants.py index 3743c981617a..cc10d6ed3407 100644 --- a/services/web/server/src/simcore_service_webserver/login/_constants.py +++ b/services/web/server/src/simcore_service_webserver/login/_constants.py @@ -1,9 +1,7 @@ from typing import Final MSG_2FA_CODE_SENT: Final[str] = "A code was sent by SMS to {phone_number}." -MSG_2FA_UNAVAILABLE_OEC: Final[str] = ( - "Two-factor authentication is temporarily unavailable. Please try again later. ({error_code})" -) +MSG_2FA_UNAVAILABLE: Final[str] = "Two-factor authentication is temporarily unavailable" MSG_ACTIVATED: Final[str] = "Your account has been activated." MSG_ACTIVATION_REQUIRED: Final[str] = ( "Please activate your account via the email we sent before logging in." diff --git a/services/web/server/src/simcore_service_webserver/login/_registration.py b/services/web/server/src/simcore_service_webserver/login/_registration.py index 2cbabf647060..0e924def7b18 100644 --- a/services/web/server/src/simcore_service_webserver/login/_registration.py +++ b/services/web/server/src/simcore_service_webserver/login/_registration.py @@ -1,7 +1,7 @@ -""" Core functionality and tools for user's registration +"""Core functionality and tools for user's registration - - registration code - - invitation code +- registration code +- invitation code """ import logging @@ -214,9 +214,7 @@ def _invitations_request_context(invitation_code: str) -> Iterator[URL]: except (ValidationError, InvalidInvitationError) as err: error_code = create_error_code(err) - user_error_msg = ( - f"Invalid invitation. {MSG_INVITATIONS_CONTACT_SUFFIX} [{error_code}]" - ) + user_error_msg = f"Invalid invitation. {MSG_INVITATIONS_CONTACT_SUFFIX}" _logger.exception( **create_troubleshotting_log_kwargs( @@ -233,7 +231,7 @@ def _invitations_request_context(invitation_code: str) -> Iterator[URL]: except InvitationsServiceUnavailableError as err: error_code = create_error_code(err) - user_error_msg = f"Unable to process your invitation since the invitations service is currently unavailable [{error_code}]" + user_error_msg = "Unable to process your invitation since the invitations service is currently unavailable" _logger.exception( **create_troubleshotting_log_kwargs( diff --git a/services/web/server/src/simcore_service_webserver/login/errors.py b/services/web/server/src/simcore_service_webserver/login/errors.py index 56588b87df68..835c971d312f 100644 --- a/services/web/server/src/simcore_service_webserver/login/errors.py +++ b/services/web/server/src/simcore_service_webserver/login/errors.py @@ -7,13 +7,12 @@ from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON from ..errors import WebServerBaseError -from ._constants import MSG_2FA_UNAVAILABLE_OEC +from ._constants import MSG_2FA_UNAVAILABLE _logger = logging.getLogger(__name__) -class LoginError(WebServerBaseError, ValueError): - ... +class LoginError(WebServerBaseError, ValueError): ... class SendingVerificationSmsError(LoginError): @@ -32,7 +31,7 @@ async def wrapper(request: web.Request) -> web.StreamResponse: except (SendingVerificationSmsError, SendingVerificationEmailError) as exc: error_code = exc.error_code() - front_end_msg = MSG_2FA_UNAVAILABLE_OEC.format(error_code=error_code) + front_end_msg = MSG_2FA_UNAVAILABLE # in these cases I want to log the cause _logger.exception( **create_troubleshotting_log_kwargs( diff --git a/services/web/server/src/simcore_service_webserver/login/handlers_confirmation.py b/services/web/server/src/simcore_service_webserver/login/handlers_confirmation.py index 6ae9f3175110..f4b9bb755a8b 100644 --- a/services/web/server/src/simcore_service_webserver/login/handlers_confirmation.py +++ b/services/web/server/src/simcore_service_webserver/login/handlers_confirmation.py @@ -187,8 +187,7 @@ async def validate_confirmation_and_redirect(request: web.Request): error_code = create_error_code(err) user_error_msg = ( f"Sorry, we cannot confirm your {action}." - "Please try again in a few moments. " - f"If the problem persist please contact support attaching this code ({error_code})" + "Please try again in a few moments." ) _logger.exception( diff --git a/services/web/server/src/simcore_service_webserver/login/handlers_registration.py b/services/web/server/src/simcore_service_webserver/login/handlers_registration.py index 541218ae9975..e91556f4424b 100644 --- a/services/web/server/src/simcore_service_webserver/login/handlers_registration.py +++ b/services/web/server/src/simcore_service_webserver/login/handlers_registration.py @@ -274,7 +274,7 @@ async def register(request: web.Request): ) except Exception as err: # pylint: disable=broad-except error_code = create_error_code(err) - user_error_msg = f"{MSG_CANT_SEND_MAIL} [{error_code}]" + user_error_msg = MSG_CANT_SEND_MAIL _logger.exception( **create_troubleshotting_log_kwargs( @@ -416,7 +416,7 @@ async def register_phone(request: web.Request): except Exception as err: # pylint: disable=broad-except # Unhandled errors -> 503 error_code = create_error_code(err) - user_error_msg = f"Currently we cannot register phone numbers [{error_code}]" + user_error_msg = "Currently we cannot register phone numbers" _logger.exception( **create_troubleshotting_log_kwargs( diff --git a/services/web/server/src/simcore_service_webserver/users/_users_rest.py b/services/web/server/src/simcore_service_webserver/users/_users_rest.py index b83c33db6003..6b3f00ac1b07 100644 --- a/services/web/server/src/simcore_service_webserver/users/_users_rest.py +++ b/services/web/server/src/simcore_service_webserver/users/_users_rest.py @@ -59,8 +59,7 @@ MissingGroupExtraPropertiesForProductError: HttpErrorInfo( status.HTTP_503_SERVICE_UNAVAILABLE, "The product is not ready for use until the configuration is fully completed. " - "Please wait and try again. " - "If this issue persists, contact support indicating this support code: {error_code}.", + "Please wait and try again. ", ), } diff --git a/services/web/server/src/simcore_service_webserver/wallets/_handlers.py b/services/web/server/src/simcore_service_webserver/wallets/_handlers.py index cc2833ecfd27..22d085b90f1e 100644 --- a/services/web/server/src/simcore_service_webserver/wallets/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/wallets/_handlers.py @@ -97,7 +97,7 @@ async def wrapper(request: web.Request) -> web.StreamResponse: except BillingDetailsNotFoundError as exc: error_code = create_error_code(exc) - user_error_msg = f"{MSG_BILLING_DETAILS_NOT_DEFINED_ERROR} [{error_code}]" + user_error_msg = MSG_BILLING_DETAILS_NOT_DEFINED_ERROR _logger.exception( **create_troubleshotting_log_kwargs( @@ -155,10 +155,10 @@ async def create_wallet(request: web.Request): async def list_wallets(request: web.Request): req_ctx = WalletsRequestContext.model_validate(request) - wallets: list[ - WalletGetWithAvailableCredits - ] = await _api.list_wallets_with_available_credits_for_user( - app=request.app, user_id=req_ctx.user_id, product_name=req_ctx.product_name + wallets: list[WalletGetWithAvailableCredits] = ( + await _api.list_wallets_with_available_credits_for_user( + app=request.app, user_id=req_ctx.user_id, product_name=req_ctx.product_name + ) ) return envelope_json_response(wallets) diff --git a/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa.py b/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa.py index 30f05227ab26..588e95182b61 100644 --- a/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa.py +++ b/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa.py @@ -32,7 +32,7 @@ ) from simcore_service_webserver.login._constants import ( CODE_2FA_SMS_CODE_REQUIRED, - MSG_2FA_UNAVAILABLE_OEC, + MSG_2FA_UNAVAILABLE, ) from simcore_service_webserver.login.storage import AsyncpgStorage from simcore_service_webserver.products import products_web @@ -456,9 +456,7 @@ async def test_2fa_sms_failure_during_login( response, status.HTTP_503_SERVICE_UNAVAILABLE ) assert not data - assert error["errors"][0]["message"].startswith( - MSG_2FA_UNAVAILABLE_OEC[:10] - ) + assert error["errors"][0]["message"].startswith(MSG_2FA_UNAVAILABLE[:10]) # Expects logs like 'Failed while setting up 2FA code and sending SMS to 157XXXXXXXX3 [OEC:140392495277888]' assert f"{fake_user_phone_number[:3]}" in caplog.text