Skip to content

Commit

Permalink
Merge branch 'feature/user_notifications' of https://github.com/OpenM…
Browse files Browse the repository at this point in the history
…ined/PySyft into feature/user_notifications
  • Loading branch information
jcardonnet committed Feb 26, 2024
2 parents 6f0d664 + 11c470e commit 286a328
Show file tree
Hide file tree
Showing 16 changed files with 326 additions and 41 deletions.
1 change: 1 addition & 0 deletions packages/grid/backend/grid/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def get_emails_enabled(cls, v: bool, values: Dict[str, Any]) -> bool:
CONSUMER_SERVICE_NAME: Optional[str] = os.getenv("CONSUMER_SERVICE_NAME")
INMEMORY_WORKERS: bool = str_to_bool(os.getenv("INMEMORY_WORKERS", True))
SMTP_USERNAME: str = os.getenv("SMTP_USERNAME", "")
EMAIL_SENDER: str = os.getenv("EMAIL_SENDER", "")
SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD", "")

TEST_MODE: bool = (
Expand Down
1 change: 1 addition & 0 deletions packages/grid/backend/grid/core/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,5 @@ def seaweedfs_config() -> SeaweedFSConfig:
in_memory_workers=settings.INMEMORY_WORKERS,
smtp_username=settings.SMTP_USERNAME,
smtp_password=settings.SMTP_PASSWORD,
email_sender=settings.EMAIL_SENDER,
)
5 changes: 2 additions & 3 deletions packages/grid/default.env
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ DEFAULT_ROOT_PASSWORD=changethis
SMTP_TLS=True
SMTP_PORT=587
SMTP_HOST=
SMTP_USER=
SMTP_USERNAME=
SMTP_PASSWORD=
[email protected]
EMAIL_SENDER=
SERVER_HOST="https://${DOMAIN}"
NETWORK_CHECK_INTERVAL=60
DOMAIN_CHECK_INTERVAL=60
Expand All @@ -56,7 +56,6 @@ QUEUE_PORT=5556
CREATE_PRODUCER=False
N_CONSUMERS=1
INMEMORY_WORKERS=True
EMAIL_TOKEN=""

# New Service Flag
USE_NEW_SERVICE=False
Expand Down
8 changes: 6 additions & 2 deletions packages/grid/helm/syft/templates/backend-statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,14 @@ spec:
value: "true"
- name: N_CONSUMERS
value: "0"
- name: SMTP_USERNAME
value: {{ .Values.node.settings.smtpUserName }}
- name: SMTP_PASSWORD
value: {{ .Values.node.settings.smtpPassword }}
- name: EMAIL_SENDER
value: {{ .Values.node.settings.smtpSender }}
- name: INMEMORY_WORKERS
value: "{{ .Values.node.settings.inMemoryWorkers }}"
- name: EMAIL_TOKEN
value: {{ .Values.node.settings.emailToken }}
- name: LOG_LEVEL
value: {{ .Values.node.settings.logLevel }}
- name: DEFAULT_WORKER_POOL_IMAGE
Expand Down
3 changes: 3 additions & 0 deletions packages/grid/helm/syft/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ node:
versionHash: "abc"
nodeSideType: "high"
defaultRootEmail: "[email protected]"
smtpUserName: "apikey"
smtpPassword: "password"
smtpSender: "[email protected]"
logLevel: "info"
inMemoryWorkers: false
defaultWorkerPoolCount: 1
Expand Down
10 changes: 10 additions & 0 deletions packages/hagrid/hagrid/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,13 @@ def clean(location: str) -> None:
type=str,
help="Password used to auth in email server and enable notification via emails",
)
@click.option(
"--smtp-sender",
default=None,
required=False,
type=str,
help="Sender email used to deliver PyGrid email notifications.",
)
@click.option(
"--build-src",
default=DEFAULT_BRANCH,
Expand Down Expand Up @@ -1325,6 +1332,7 @@ def create_launch_cmd(

parsed_kwargs["smtp_username"] = kwargs["smtp_username"]
parsed_kwargs["smtp_password"] = kwargs["smtp_password"]
parsed_kwargs["smtp_sender"] = kwargs["smtp_sender"]

parsed_kwargs["enable_warnings"] = not kwargs["no_warnings"]

Expand Down Expand Up @@ -2174,6 +2182,7 @@ def create_launch_docker_cmd(
single_container_mode = kwargs["deployment_type"] == "single_container"
in_mem_workers = kwargs.get("in_mem_workers")
smtp_username = kwargs.get("smtp_username")
smtp_sender = kwargs.get("smtp_sender")
smtp_password = kwargs.get("smtp_password")

enable_oblv = bool(kwargs["oblv"])
Expand Down Expand Up @@ -2241,6 +2250,7 @@ def create_launch_docker_cmd(
"INMEMORY_WORKERS": in_mem_workers,
"SMTP_USERNAME": smtp_username,
"SMTP_PASSWORD": smtp_password,
"EMAIL_SENDER": smtp_sender,
}

if "trace" in kwargs and kwargs["trace"] is True:
Expand Down
7 changes: 5 additions & 2 deletions packages/syft/src/syft/node/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def __init__(
in_memory_workers: bool = True,
smtp_username: Optional[str] = None,
smtp_password: Optional[str] = None,
email_token: Optional[str] = None,
email_sender: Optional[str] = None,
):
# 🟡 TODO 22: change our ENV variable format and default init args to make this
# less horrible or add some convenience functions
Expand Down Expand Up @@ -400,7 +400,10 @@ def __init__(
)

NotifierService.init_notifier(
node=self, email_password=smtp_password, email_username=smtp_username
node=self,
email_password=smtp_password,
email_username=smtp_username,
email_sender=email_sender,
)

self.client_cache = {}
Expand Down
219 changes: 219 additions & 0 deletions packages/syft/src/syft/service/notification/email_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# stdlib
from typing import TYPE_CHECKING

# relative
from ..context import AuthedServiceContext

if TYPE_CHECKING:
# relative
from .notifications import Notification


class EmailTemplate:
pass


class OnBoardEmailTemplate(EmailTemplate):
@staticmethod
def email_title(notification: "Notification", context: AuthedServiceContext) -> str:
return f"Welcome to {context.node.name} node!"

@staticmethod
def email_body(notification: "Notification", context: AuthedServiceContext) -> str:
user_service = context.node.get_service("userservice")
admin_name = user_service.get_by_verify_key(
user_service.admin_verify_key()
).name

head = (
f"""
<head>
<title>Welcome to {context.node.name}</title>
"""
+ """
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
line-height: 1.6;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background: #fff;
}
h1 {
color: #0056b3;
}
.feature {
background-color: #e7f1ff;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.footer {
text-align: center;
font-size: 14px;
color: #aaa;
}
</style>
</head>
"""
)

body = f"""
<body>
<div class="container">
<h1>Welcome to {context.node.name} node!</h1>
<p>Hello,</p>
<p>We're thrilled to have you on board and
excited to help you get started with our powerful features:</p>
<div class="feature">
<h3>Remote Data Science</h3>
<p>Access and analyze data from anywhere, using our comprehensive suite of data science tools.</p>
</div>
<div class="feature">
<h3>Remote Code Execution</h3>
<p>Execute code remotely on private data, ensuring flexibility and efficiency in your research.</p>
</div>
<!-- Add more features here if needed -->
<p>Explore these features and much more within your account.
If you have any questions or need assistance, don't hesitate to reach out.</p>
<p>Cheers,</p>
<p>{admin_name}</p>
<div class="footer">
This is an automated message, please do not reply directly to this email. <br>
For assistance, please contact our support team.
</div>
</div>
</body>
"""
return f"""<html>{head} {body}</html>"""


class RequestEmailTemplate(EmailTemplate):
@staticmethod
def email_title(notification: "Notification", context: AuthedServiceContext) -> str:
return f"Domain {context.node.name}: New Request!"

@staticmethod
def email_body(notification: "Notification", context: AuthedServiceContext) -> str:
request_obj = notification.linked_obj.resolve_with_context(context=context).ok()

head = """
<head>
<title>Access Request Notification</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
font-size: 24px;
color: #333;
text-align: center;
}
.content {
font-size: 16px;
line-height: 1.6;
}
.request-card {
background-color: #ffffff;
border: 1px solid #ddd;
padding: 15px;
margin-top: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.request-header {
font-size: 18px;
color: #333;
margin-bottom: 10px;
font-weight: bold;
}
.request-content {
font-size: 14px;
line-height: 1.5;
color: #555;
}
.button {
display: block;
width: max-content;
background-color: #007bff;
color: white;
padding: 10px 20px;
text-align: center;
text-color: white;
border-radius: 5px;
text-decoration: none;
font-weight: bold;
margin: 20px auto;
}
.footer {
text-align: center;
font-size: 14px;
color: #aaa;
}
</style>
</head>"""

body = f"""
<body>
<div class="container">
<div class="header">
Request Notification
</div>
<div class="content">
<p>Hello,</p>
<p>A new request has been submitted and requires your attention.
Please review the details below:</p>
<div class="request-card">
<div class="request-header">Request Details</div>
<div class="request-content">
<p><strong>ID:</strong> {request_obj.id}</p>
<p>
<strong>Submitted By:</strong>
{request_obj.requesting_user_name} {request_obj.requesting_user_email or ""}
</p>
<p><strong>Date:</strong> {request_obj.request_time}</p>
<p>
<strong>Changes:</strong>
{",".join([change.__class__.__name__ for change in request_obj.changes])}
</p>
</div>
</div>
<p>If you did not expect this request or have concerns about it,
please contact our support team immediately.</p>
</div>
<div class="footer">
This is an automated message, please do not reply directly to this email. <br>
For assistance, please contact our support team.
</div>
</div>
</body>
"""
return f"""<html>{head} {body}</html>"""
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def send(
self, context: AuthedServiceContext, notification: CreateNotification
) -> Union[Notification, SyftError]:
"""Send a new notification"""

new_notification = notification.to(Notification, context=context)

# Add read permissions to person receiving this message
Expand All @@ -55,7 +54,7 @@ def send(
)
notifier_service = context.node.get_service("notifierservice")

res = notifier_service.dispatch_notification(context.node, new_notification)
res = notifier_service.dispatch_notification(context, new_notification)
if isinstance(res, SyftError):
return res

Expand Down
3 changes: 3 additions & 0 deletions packages/syft/src/syft/service/notification/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import Enum
from typing import List
from typing import Optional
from typing import Type

# relative
from ...client.api import APIRegistry
Expand All @@ -24,6 +25,7 @@
from ...util import options
from ...util.colors import SURFACE
from ..notifier.notifier_enums import NOTIFIERS
from .email_templates import EmailTemplate


@serializable()
Expand Down Expand Up @@ -139,6 +141,7 @@ class Notification(SyftObject):
status: NotificationStatus = NotificationStatus.UNREAD
linked_obj: Optional[LinkedObject]
notifier_types: Optional[List[NOTIFIERS]] = []
email_template: Optional[Type[EmailTemplate]] = None
replies: Optional[List[ReplyNotification]] = []

__attr_searchable__ = [
Expand Down
Loading

0 comments on commit 286a328

Please sign in to comment.