diff --git a/packages/syft/src/syft/service/notification/email_templates.py b/packages/syft/src/syft/service/notification/email_templates.py index 5710991fdcc..1a2471833b0 100644 --- a/packages/syft/src/syft/service/notification/email_templates.py +++ b/packages/syft/src/syft/service/notification/email_templates.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: # relative + from ..request.request import Request from .notifications import Notification @@ -662,8 +663,11 @@ def email_title(notification: "Notification", context: AuthedServiceContext) -> @staticmethod def email_body(notification: "Notification", context: AuthedServiceContext) -> str: + # relative + from ..request.request import RequestStatus + notification.linked_obj = cast(LinkedObject, notification.linked_obj) - request_obj = notification.linked_obj.resolve_with_context( + request_obj: Request = notification.linked_obj.resolve_with_context( context=context ).unwrap() badge_color = "red" if request_obj.status.name == "REJECTED" else "green" @@ -672,11 +676,26 @@ def email_body(notification: "Notification", context: AuthedServiceContext) -> s request_name = request_obj.requesting_user_name request_email = request_obj.requesting_user_email request_time = request_obj.request_time - request_status = request_obj.status.name # fails in l0 check right now + request_status = request_obj.status + request_status_name = request_status.name # fails in l0 check right now request_changes = ",".join( [change.__class__.__name__ for change in request_obj.changes] ) + deny_reason_html = "" + if request_status == RequestStatus.REJECTED: + deny_reason_or_err = request_obj.get_deny_reason(context=context) + if deny_reason_or_err.is_err(): + deny_reason = None + deny_reason = deny_reason_or_err.unwrap() + + if not isinstance(deny_reason, str) or not len(deny_reason): + deny_reason = ( + "No reason provided, please contact the admin for more information." + ) + + deny_reason_html = f"

Deny Reason: {deny_reason}

" + head = """ Access Request Notification @@ -792,8 +811,10 @@ def email_body(notification: "Notification", context: AuthedServiceContext) -> s

Date: {request_time}

Status:

- {request_status} -
+ {request_status_name} + + + {deny_reason_html}

Changes: {request_changes} diff --git a/packages/syft/src/syft/service/request/request.py b/packages/syft/src/syft/service/request/request.py index c898c73fde0..7c5495a756e 100644 --- a/packages/syft/src/syft/service/request/request.py +++ b/packages/syft/src/syft/service/request/request.py @@ -455,7 +455,28 @@ def html_description(self) -> str: return desc + @property + def deny_reason(self) -> str | SyftError: + code = self.code + if isinstance(code, SyftError): + return code + + code_status: UserCodeStatusCollection = code.status_link.resolve + return code_status.first_denial_reason + + @as_result(SyftException) + def get_deny_reason(self, context: AuthedServiceContext) -> str | None: + code = self.get_user_code(context).unwrap() + if code is None: + return None + + code_status = code.get_status(context).unwrap() + return code_status.first_denial_reason + def _coll_repr_(self) -> dict[str, str | dict[str, str]]: + # relative + from ...util.notebook_ui.components.sync import Badge + if self.status == RequestStatus.APPROVED: badge_color = "badge-green" elif self.status == RequestStatus.PENDING: @@ -463,7 +484,18 @@ def _coll_repr_(self) -> dict[str, str | dict[str, str]]: else: badge_color = "badge-red" - status_badge = {"value": self.status.name.capitalize(), "type": badge_color} + status_badge = Badge( + value=self.status.name.capitalize(), + badge_class=badge_color, + ).to_html() + + if self.status == RequestStatus.REJECTED: + deny_reason = self.deny_reason + if isinstance(deny_reason, str) and len(deny_reason) > 0: + status_badge += ( + "
" + f"Deny Reason: {deny_reason}" + ) user_data = [ self.requesting_user_name, @@ -505,10 +537,11 @@ def codes(self) -> Any: message="This type of request does not have code associated with it." ) + @as_result(SyftException) def get_user_code(self, context: AuthedServiceContext) -> UserCode | None: for change in self.changes: if isinstance(change, UserCodeStatusChange): - return change.get_user_code(context) + return change.get_user_code(context).unwrap() return None @property @@ -641,7 +674,7 @@ def is_l0_deployment(self) -> bool: return bool(self.code) and self.code.is_l0_deployment def get_is_l0_deployment(self, context: AuthedServiceContext) -> bool: - code = self.get_user_code(context) + code = self.get_user_code(context).unwrap() if code: return code.is_l0_deployment else: @@ -1355,6 +1388,7 @@ def code(self) -> UserCode: return self.linked_user_code._resolve_cache return self.linked_user_code.resolve + @as_result(SyftException) def get_user_code(self, context: AuthedServiceContext) -> UserCode: return self.linked_user_code.resolve_with_context(context).unwrap()