Skip to content

Commit 836fe49

Browse files
pcrespovmatusdrobuliak66
authored andcommitted
cherry pick AI-assisted workflow for user-facing messages #7345 (needed because of user_message function)
1 parent 5dcecb4 commit 836fe49

File tree

21 files changed

+261
-75
lines changed

21 files changed

+261
-75
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ This document provides guidelines and best practices for using GitHub Copilot in
5151

5252
- [Python Coding Conventions](../docs/coding-conventions.md)
5353
- [Environment Variables Guide](../docs/env-vars.md)
54-
- [Steps to Upgrade Python](../docs/steps-to-upgrade-python.md)
5554
- [Pydantic Annotated fields](../docs/llm-prompts/pydantic-annotated-fields.md)
55+
- [Steps to Upgrade Python](../docs/steps-to-upgrade-python.md)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
mode: 'edit'
3+
description: 'Update user messages'
4+
---
5+
6+
This prompt guide is for updating user-facing messages in ${file} or ${selection}
7+
8+
## What is a User Message?
9+
10+
A user message is any string that will be displayed to end-users of the application.
11+
In our codebase, these messages are marked with the `user_message` function:
12+
13+
```python
14+
from common_library.user_messages import user_message
15+
16+
error_msg = user_message("Operation failed. Please try again later.")
17+
```
18+
19+
## Guidelines for Updating User Messages
20+
21+
When modifying user messages, follow these rules:
22+
23+
1. **Version Tracking**: Every modification to a user message must include an incremented `_version` parameter:
24+
25+
```python
26+
# Before modification
27+
user_message("Error: Unable to connect to the server.")
28+
29+
# After modification, add _version or increment it if it already exists
30+
user_message("Currently unable to establish connection to the server.", _version=1)
31+
```
32+
33+
2. **F-String Preservation**: When modifying messages that use f-strings, preserve all parameters and their formatting:
34+
35+
```python
36+
# Before
37+
user_message(f"Project {project_name} could not be loaded.")
38+
39+
# After (correct)
40+
user_message(f"Unable to load project {project_name}.", _version=1)
41+
42+
# After (incorrect - lost the parameter)
43+
user_message("Unable to load project.", _version=1)
44+
```
45+
46+
3. **Message Style**: Follow *strictly* the guidelines in `${workspaceFolder}/docs/user-messages-guidelines.md`
47+
48+
4. **Preserve Context**: Ensure the modified message conveys the same meaning and context as the original.
49+
50+
5. **Incremental Versioning**: If a message already has a version, increment it by 1:
51+
52+
```python
53+
# Before
54+
user_message("Session expired.", _version=2)
55+
56+
# After
57+
user_message("Your session has expired. Please log in again.", _version=3)
58+
```
59+
60+
## Examples
61+
62+
### Example 1: Simple Message Update
63+
64+
```python
65+
# Before
66+
error_dialog(user_message("Failed to save changes."))
67+
68+
# After
69+
error_dialog(user_message("Unable to save your changes. Please try again.", _version=1))
70+
```
71+
72+
### Example 2: F-string Message Update
73+
74+
```python
75+
# Before
76+
raise ValueError(user_message(f"Invalid input parameter: {param_name}"))
77+
78+
# After
79+
raise ValueError(user_message(f"The parameter '{param_name}' contains a value that is not allowed.", _version=1))
80+
```
81+
82+
### Example 3: Already Versioned Message
83+
84+
```python
85+
# Before
86+
return HttpErrorInfo(status.HTTP_404_NOT_FOUND, user_message("User not found.", _version=1))
87+
88+
# After
89+
return HttpErrorInfo(status.HTTP_404_NOT_FOUND, user_message("The requested user could not be found.", _version=2))
90+
```
91+
92+
Remember: The goal is to improve clarity and helpfulness for end-users while maintaining accurate versioning for tracking changes.

docs/messages-guidelines.md renamed to docs/user-messages-guidelines.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Error and Warning Message Guidelines
1+
# User Message Guidelines
22

3-
These guidelines ensure that messages are user-friendly, clear, and helpful while maintaining a professional tone. 🚀
3+
These guidelines ensure that error and warnings user-facing messages are user-friendly, clear, and helpful while maintaining a professional tone. 🚀
44

55
Some details:
66

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
def user_message(msg: str, *, _version: int | None = None) -> str:
2+
"""Marks a message as user-facing
3+
4+
Arguments:
5+
msg -- human-friendly string that follows docs/user-messages-guidelines.md
6+
_version -- version number to track changes to messages; increment when modifying an existing message
7+
8+
Returns:
9+
The original message string, allowing it to be used inline in code
10+
"""
11+
return msg
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from common_library.user_messages import user_message
2+
3+
4+
def test_user_message() -> None:
5+
6+
assert user_message("This is a user message") == "This is a user message"

packages/service-library/src/servicelib/aiohttp/rest_middlewares.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from aiohttp.web_response import StreamResponse
1414
from common_library.error_codes import ErrorCodeStr, create_error_code
1515
from common_library.json_serialization import json_dumps, json_loads
16+
from common_library.user_messages import user_message
1617
from models_library.basic_types import IDStr
1718
from models_library.rest_error import ErrorGet, ErrorItemType, LogMessageType
1819
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
@@ -33,7 +34,7 @@
3334
from .web_exceptions_extension import get_http_error_class_or_none
3435

3536
DEFAULT_API_VERSION = "v0"
36-
_FMSG_INTERNAL_ERROR_USER_FRIENDLY = (
37+
_FMSG_INTERNAL_ERROR_USER_FRIENDLY = user_message(
3738
"We apologize for the inconvenience. "
3839
"The issue has been recorded, please report it if it persists."
3940
)

services/web/server/src/simcore_service_webserver/api_keys/_controller/rest_exceptions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from common_library.user_messages import user_message
12
from servicelib.aiohttp import status
23

34
from ...exception_handling import (
@@ -11,11 +12,11 @@
1112
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
1213
ApiKeyDuplicatedDisplayNameError: HttpErrorInfo(
1314
status.HTTP_409_CONFLICT,
14-
"API key display name duplicated",
15+
user_message("An API key with this display name already exists", _version=1),
1516
),
1617
ApiKeyNotFoundError: HttpErrorInfo(
1718
status.HTTP_404_NOT_FOUND,
18-
"API key was not found",
19+
user_message("The requested API key could not be found", _version=1),
1920
),
2021
}
2122

services/web/server/src/simcore_service_webserver/catalog/_controller_rest_exceptions.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from aiohttp import web
66
from common_library.error_codes import create_error_code
7+
from common_library.user_messages import user_message
78
from models_library.rest_error import ErrorGet
89
from servicelib.aiohttp import status
910
from servicelib.logging_errors import create_troubleshootting_log_kwargs
@@ -85,22 +86,35 @@ async def _handler_catalog_client_errors(
8586
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
8687
RemoteMethodNotRegisteredError: HttpErrorInfo(
8788
status.HTTP_503_SERVICE_UNAVAILABLE,
88-
MSG_CATALOG_SERVICE_UNAVAILABLE,
89+
user_message(
90+
"The catalog service is temporarily unavailable. Please try again later.",
91+
_version=2,
92+
),
8993
),
9094
CatalogForbiddenError: HttpErrorInfo(
9195
status.HTTP_403_FORBIDDEN,
92-
"Forbidden catalog access",
96+
user_message(
97+
"Access denied: You don't have permission to view this catalog item.",
98+
_version=2,
99+
),
93100
),
94101
CatalogItemNotFoundError: HttpErrorInfo(
95102
status.HTTP_404_NOT_FOUND,
96-
"Catalog item not found",
103+
user_message(
104+
"This catalog item does not exist or has been removed.", _version=2
105+
),
97106
),
98107
DefaultPricingPlanNotFoundError: HttpErrorInfo(
99108
status.HTTP_404_NOT_FOUND,
100-
"Default pricing plan not found",
109+
user_message(
110+
"No default pricing plan is available for this operation.", _version=2
111+
),
101112
),
102113
DefaultPricingUnitForServiceNotFoundError: HttpErrorInfo(
103-
status.HTTP_404_NOT_FOUND, "Default pricing unit not found"
114+
status.HTTP_404_NOT_FOUND,
115+
user_message(
116+
"No default pricing unit is defined for this service.", _version=2
117+
),
104118
),
105119
}
106120

services/web/server/src/simcore_service_webserver/constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import Final
44

5+
from common_library.user_messages import user_message
56
from servicelib.aiohttp.application_keys import (
67
APP_AIOPG_ENGINE_KEY,
78
APP_CONFIG_KEY,
@@ -42,12 +43,11 @@
4243
"Under development. Use WEBSERVER_DEV_FEATURES_ENABLED=1 to enable current implementation"
4344
)
4445

45-
4646
# Request storage keys
4747
RQ_PRODUCT_KEY: Final[str] = f"{__name__}.RQ_PRODUCT_KEY"
4848

4949

50-
MSG_TRY_AGAIN_OR_SUPPORT: Final[str] = (
50+
MSG_TRY_AGAIN_OR_SUPPORT: Final[str] = user_message(
5151
"Please try again shortly. If the issue persists, contact support."
5252
)
5353

services/web/server/src/simcore_service_webserver/director_v2/_controller/_rest_exceptions.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from aiohttp import web
44
from common_library.error_codes import create_error_code
5+
from common_library.user_messages import user_message
56
from models_library.rest_error import ErrorGet
67
from servicelib import status_codes_utils
78
from servicelib.aiohttp import status
@@ -43,10 +44,11 @@ async def _handler_director_service_error_as_503_or_4xx(
4344
if status_codes_utils.is_5xx_server_error(exception.status):
4445
# NOTE: All directorv2 5XX are mapped to 503
4546
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
46-
user_msg = (
47+
user_msg = user_message(
4748
# Most likely the director service is down or misconfigured so the user is asked to try again later.
48-
"This service is temporarily unavailable. The incident was logged and will be investigated. "
49-
+ MSG_TRY_AGAIN_OR_SUPPORT
49+
"This service is temporarily unavailable. The incident has been logged and will be investigated. "
50+
+ MSG_TRY_AGAIN_OR_SUPPORT,
51+
_version=1,
5052
)
5153

5254
# Log for further investigation
@@ -85,11 +87,11 @@ async def _handler_director_service_error_as_503_or_4xx(
8587
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
8688
UserDefaultWalletNotFoundError: HttpErrorInfo(
8789
status.HTTP_404_NOT_FOUND,
88-
"Default wallet not found but necessary for computations",
90+
user_message("Default wallet not found but necessary for computations"),
8991
),
9092
WalletNotEnoughCreditsError: HttpErrorInfo(
9193
status.HTTP_402_PAYMENT_REQUIRED,
92-
"Wallet does not have enough credits for computations. {reason}",
94+
user_message("Wallet does not have enough credits for computations. {reason}"),
9395
),
9496
}
9597

0 commit comments

Comments
 (0)