Skip to content

Commit 33c7ac2

Browse files
Add user visible errors when permission error and request dismission happen (#126)
* handle permission error and messages to dismissed requests * fix * commit * fix * debug * debug * debug * debug * debug * debug * fix * qa
1 parent d44202c commit 33c7ac2

File tree

5 files changed

+75
-35
lines changed

5 files changed

+75
-35
lines changed

alembic/versions/a4e8be715296_add_deleted_as_new_status.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
Create Date: 2024-07-25 13:13:11.955119
66
77
"""
8-
import sqlalchemy as sa
98

10-
from sqlalchemy.dialects.postgresql import ENUM
9+
import sqlalchemy as sa
1110

1211
from alembic import op
1312

cads_broker/database.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -641,13 +641,25 @@ def set_successful_request(
641641
return request
642642

643643

644-
def set_dismissed_request(request_uid: str, session: sa.orm.Session) -> SystemRequest:
644+
def set_dismissed_request(
645+
request_uid: str,
646+
session: sa.orm.Session,
647+
message: str = "Dismissed by the user.",
648+
reason: str = "DismissedRequest",
649+
) -> SystemRequest:
645650
request = get_request(request_uid=request_uid, session=session)
646651
metadata = dict(request.request_metadata)
647-
metadata.update({"previous_status": request.status})
652+
metadata.update(
653+
{
654+
"dismission": {
655+
"previous_status": request.status,
656+
"message": message,
657+
"reason": reason,
658+
}
659+
}
660+
)
648661
request.request_metadata = metadata
649662
request.status = "dismissed"
650-
request.response_error = {"reason": "Dismissed by the user"}
651663
session.commit()
652664
logger.info("dismissed job by the user.", **logger_kwargs(request=request))
653665
return request

cads_broker/dispatcher.py

+29-14
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,34 @@ def set_request_error_status(
327327
)
328328
return request
329329

330+
def manage_dismissed_request(self, request, session):
331+
dismission_metadata = request.request_metadata.get("dismission", {})
332+
db.add_event(
333+
event_type="user_visible_error",
334+
request_uid=request.request_uid,
335+
message=dismission_metadata.get("message", ""),
336+
session=session,
337+
)
338+
previous_status = dismission_metadata.get("previous_status", "accepted")
339+
if previous_status == "running":
340+
self.qos.notify_end_of_request(
341+
request, session, scheduler=self.internal_scheduler
342+
)
343+
request.status = "deleted"
344+
elif previous_status == "accepted":
345+
self.queue.pop(request.request_uid, None)
346+
self.qos.notify_dismission_of_request(
347+
request, session, scheduler=self.internal_scheduler
348+
)
349+
if (
350+
reason := dismission_metadata.get("reason", "DismissedRequest")
351+
) == "DismissedRequest":
352+
request.status = "deleted"
353+
elif reason == "PermissionError":
354+
request.status = "failed"
355+
request.finished_at = datetime.datetime.now()
356+
return session
357+
330358
@cachetools.cachedmethod(lambda self: self.ttl_cache)
331359
@perf_logger
332360
def sync_database(self, session: sa.orm.Session) -> None:
@@ -346,20 +374,7 @@ def sync_database(self, session: sa.orm.Session) -> None:
346374
for request in dismissed_requests:
347375
if future := self.futures.pop(request.request_uid, None):
348376
future.cancel()
349-
if (
350-
previous_status := request.request_metadata.get(
351-
"previous_status", "accepted"
352-
)
353-
) == "running":
354-
self.qos.notify_end_of_request(
355-
request, session, scheduler=self.internal_scheduler
356-
)
357-
elif previous_status == "accepted":
358-
self.queue.pop(request.request_uid, None)
359-
self.qos.notify_dismission_of_request(
360-
request, session, scheduler=self.internal_scheduler
361-
)
362-
request.status = "deleted"
377+
session = self.manage_dismissed_request(request, session)
363378
session.commit()
364379

365380
statement = sa.select(db.SystemRequest).where(

cads_broker/entry_points.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,13 @@ class RequestStatus(str, Enum):
118118
@app.command()
119119
def delete_requests(
120120
status: RequestStatus = RequestStatus.running,
121+
user_uid: Optional[str] = None,
121122
connection_string: Optional[str] = None,
122123
minutes: float = 0,
123124
seconds: float = 0,
124125
hours: float = 0,
125126
days: float = 0,
127+
message: Optional[str] = "The request has been dismissed by the administrator.",
126128
skip_confirmation: Annotated[bool, typer.Option("--yes", "-y")] = False,
127129
) -> None:
128130
"""Set the status of records in the system_requests table to 'dismissed'.
@@ -142,7 +144,18 @@ def delete_requests(
142144
sa.update(database.SystemRequest)
143145
.where(database.SystemRequest.status == status)
144146
.where(database.SystemRequest.created_at < timestamp)
145-
.values(status="dismissed")
147+
)
148+
if user_uid:
149+
statement = statement.where(database.SystemRequest.user_uid == user_uid)
150+
statement = statement.values(
151+
status="dismissed",
152+
request_metadata={
153+
"dismission": {
154+
"reason": "DismissedRequest",
155+
"message": message,
156+
"previous_status": status,
157+
}
158+
},
146159
)
147160
number_of_requests = session.execute(statement).rowcount
148161
if not skip_confirmation:

cads_broker/qos/QoS.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,11 @@ def _properties(self, request, session):
140140
if rule.match(request):
141141
properties.permissions.append(rule)
142142
if not rule.evaluate(request):
143-
database.set_request_status(
143+
database.set_dismissed_request(
144144
request_uid=request.request_uid,
145-
status="failed",
146145
session=session,
147-
error_message=rule.info.evaluate(
148-
Context(request, self.environment)
149-
),
150-
error_reason="Permission error.",
146+
message=rule.info.evaluate(Context(request, self.environment)),
147+
reason="PermissionError",
151148
)
152149
break
153150

@@ -254,20 +251,24 @@ def user_limit(self, request):
254251

255252
limits = self.per_user_limits.get(user, [])
256253
applied_limits = []
257-
for limit in limits:
258-
if limit.match(request):
259-
applied_limits.append(limit)
254+
for user_limit in limits:
255+
if user_limit.match(request):
256+
applied_limits.append(user_limit)
260257

261-
for limit in self.rules.user_limits:
262-
if limit.match(request):
258+
for user_limit in self.rules.user_limits:
259+
if user_limit.match(request):
263260
"""
264261
We clone the rule because we need one instance per different
265262
user otherwise all users will share that limit
266263
"""
267-
if limit.get_uid(request) not in [l.get_uid(request) for l in limits]:
268-
limit = limit.clone()
269-
applied_limits.append(limit)
270-
self.per_user_limits[user] = self.per_user_limits.get(user, []) + [limit]
264+
if user_limit.get_uid(request) not in [
265+
limit.get_uid(request) for limit in limits
266+
]:
267+
user_limit = user_limit.clone()
268+
applied_limits.append(user_limit)
269+
self.per_user_limits[user] = self.per_user_limits.get(user, []) + [
270+
user_limit
271+
]
271272
return applied_limits
272273

273274
@locked

0 commit comments

Comments
 (0)