Skip to content

Commit b6937df

Browse files
authored
feat: sending email when workflow fails (#588)
1 parent 9ef28a4 commit b6937df

File tree

14 files changed

+801
-403
lines changed

14 files changed

+801
-403
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ e1faa321-35df-486b-8fa8-3601ee714011*
177177
# sqlite db
178178
*.sqlite3
179179
state/*
180-
180+
.terraform*
181181
examples/alerts/dd.yml
182182
keep-ui/node_modules
183183
keep-ui/node_modules/*

keep/api/core/db.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,11 @@ def get_api_key(api_key: str):
684684
return tenant_api_key
685685

686686

687+
def get_user_by_api_key(api_key: str):
688+
api_key = get_api_key(api_key)
689+
return api_key.created_by
690+
691+
687692
# this is only for single tenant
688693
def get_user(username, password, update_sign_in=True):
689694
from keep.api.core.dependencies import SINGLE_TENANT_UUID
@@ -770,3 +775,19 @@ def get_workflow_id_by_name(tenant_id, workflow_name):
770775

771776
if workflow:
772777
return workflow.id
778+
779+
780+
def get_previous_execution_id(tenant_id, workflow_id, workflow_execution_id):
781+
with Session(engine) as session:
782+
previous_execution = session.exec(
783+
select(WorkflowExecution)
784+
.where(WorkflowExecution.tenant_id == tenant_id)
785+
.where(WorkflowExecution.workflow_id == workflow_id)
786+
.where(WorkflowExecution.id != workflow_execution_id)
787+
.order_by(WorkflowExecution.started.desc())
788+
.limit(1)
789+
).first()
790+
if previous_execution:
791+
return previous_execution
792+
else:
793+
return None

keep/api/core/dependencies.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from sqlmodel import Session
1616

1717
from keep.api.core.config import AuthenticationType
18-
from keep.api.core.db import get_api_key, get_session
18+
from keep.api.core.db import get_api_key, get_session, get_user_by_api_key
1919

2020
logger = logging.getLogger(__name__)
2121

@@ -39,7 +39,8 @@ def get_user_email(request: Request) -> str | None:
3939
decoded_token = jwt.decode(token, options={"verify_signature": False})
4040
return decoded_token.get("email")
4141
elif "x-api-key" in request.headers:
42-
return "apikey@keephq.dev"
42+
username = get_user_by_api_key(request.headers["x-api-key"])
43+
return username
4344
else:
4445
raise HTTPException(
4546
status_code=401, detail="Invalid authentication credentials"

keep/api/models/db/tenant.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class TenantApiKey(SQLModel, table=True):
1818
tenant: Tenant = Relationship()
1919
is_system: bool = False
2020
system_description: Optional[str] = None
21+
created_by: str
2122

2223
class Config:
2324
orm_mode = True

keep/api/routes/alerts.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pusher import Pusher
88
from sqlmodel import Session
99

10+
from keep.api.core.config import config
1011
from keep.api.core.db import enrich_alert as enrich_alert_db
1112
from keep.api.core.db import get_alerts as get_alerts_from_db
1213
from keep.api.core.db import get_enrichments as get_enrichments_from_db
@@ -20,6 +21,7 @@
2021
)
2122
from keep.api.models.alert import AlertDto, DeleteRequestBody, EnrichAlertRequestBody
2223
from keep.api.models.db.alert import Alert
24+
from keep.api.utils.email_utils import EmailTemplates, send_email
2325
from keep.contextmanager.contextmanager import ContextManager
2426
from keep.providers.providers_factory import ProvidersFactory
2527
from keep.workflowmanager.workflowmanager import WorkflowManager
@@ -300,6 +302,29 @@ def assign_alert(
300302
enrichments={"assignee": user_email if not unassign else None},
301303
)
302304

305+
try:
306+
logger.info("Sending assign alert email to user")
307+
# TODO: this should be changed to dynamic url but we don't know what's the frontend URL
308+
keep_platform_url = config(
309+
"KEEP_PLATFORM_URL", default="https://platform.keephq.dev"
310+
)
311+
url = f"{keep_platform_url}/alerts?fingerprint={fingerprint}"
312+
send_email(
313+
to_email=user_email,
314+
template_id=EmailTemplates.ALERT_ASSIGNED_TO_USER,
315+
url=url,
316+
)
317+
logger.info("Sent assign alert email to user")
318+
except Exception as e:
319+
logger.exception(
320+
"Failed to send email to user",
321+
extra={
322+
"error": str(e),
323+
"tenant_id": tenant_id,
324+
"user_email": user_email,
325+
},
326+
)
327+
303328
logger.info(
304329
"Assigned alert successfully",
305330
extra={

keep/api/routes/providers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ def install_provider_webhook(
675675
webhook_api_key = get_or_create_api_key(
676676
session=session,
677677
tenant_id=tenant_id,
678+
created_by="system",
678679
unique_api_key_id="webhook",
679680
system_description="Webhooks API key",
680681
)
@@ -702,6 +703,7 @@ def get_webhook_settings(
702703
webhook_api_key = get_or_create_api_key(
703704
session=session,
704705
tenant_id=tenant_id,
706+
created_by="system",
705707
unique_api_key_id="webhook",
706708
system_description="Webhooks API key",
707709
)

keep/api/routes/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from keep.api.core.db import delete_user as delete_user_from_db
1616
from keep.api.core.db import get_session
1717
from keep.api.core.db import get_users as get_users_from_db
18-
from keep.api.core.dependencies import verify_bearer_token
18+
from keep.api.core.dependencies import get_user_email, verify_bearer_token
1919
from keep.api.models.alert import AlertDto
2020
from keep.api.models.smtp import SMTPSettings
2121
from keep.api.models.user import User
@@ -41,6 +41,7 @@ def webhook_settings(
4141
webhook_api_key = get_or_create_api_key(
4242
session=session,
4343
tenant_id=tenant_id,
44+
created_by="system",
4445
unique_api_key_id="webhook",
4546
system_description="Webhooks API key",
4647
)
@@ -275,11 +276,13 @@ def _print_debug(self, *args):
275276
def get_api_key(
276277
tenant_id: str = Depends(verify_bearer_token),
277278
session: Session = Depends(get_session),
279+
user_name: str = Depends(get_user_email),
278280
):
279281
# get the api key for the CLI
280282
api_key = get_or_create_api_key(
281283
session=session,
282284
tenant_id=tenant_id,
285+
created_by=user_name,
283286
unique_api_key_id="cli",
284287
system_description="API key",
285288
)

keep/api/utils/email_utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import enum
2+
import logging
3+
4+
from sendgrid import SendGridAPIClient
5+
from sendgrid.helpers.mail import Mail
6+
7+
from keep.api.core.config import config
8+
9+
# TODO
10+
# This is beta code. It will be changed in the future.
11+
12+
# Sending emails is currently done via SendGrid. It doesnt fit well with OSS, so we need to:
13+
# 1. Add EmailManager that will support more than just SendGrid
14+
# 2. Add support for templates/html
15+
# 3. Add support for SMTP (how will it work with templates?)
16+
17+
18+
# In the OSS - you can overwrite the template ids
19+
class EmailTemplates(enum.Enum):
20+
WORKFLOW_RUN_FAILED = config(
21+
"WORKFLOW_FAILED_EMAIL_TEMPLATE_ID",
22+
default="d-bb1b3bb30ce8460cbe6ed008701affb1",
23+
)
24+
ALERT_ASSIGNED_TO_USER = config(
25+
"ALERT_ASSIGNED_TO_USER_EMAIL_TEMPLATE_ID",
26+
default="d-58ec64ed781e4c359e18da7ad97ac750",
27+
)
28+
29+
30+
logger = logging.getLogger(__name__)
31+
32+
# CONSTS
33+
FROM_EMAIL = config("SENDGRID_FROM_EMAIL", default="platform@keephq.dev")
34+
API_KEY = config("SENDGRID_API_KEY", default=None)
35+
CC = config("SENDGRID_CC", default="founders@keephq.dev")
36+
37+
38+
def send_email(
39+
to_email: str,
40+
template_id: EmailTemplates,
41+
**kwargs,
42+
):
43+
# that's ok on OSS
44+
if not API_KEY:
45+
logger.debug("No SendGrid API key, skipping sending email")
46+
return
47+
48+
message = Mail(from_email=FROM_EMAIL, to_emails=to_email)
49+
message.template_id = template_id.value
50+
# TODO: validate the kwargs and the template parameters are the same
51+
message.dynamic_template_data = kwargs
52+
# send the email
53+
try:
54+
logger.info(f"Sending email to {to_email} with template {template_id}")
55+
sg = SendGridAPIClient(API_KEY)
56+
sg.send(message)
57+
logger.info(f"Email sent to {to_email} with template {template_id}")
58+
except Exception as e:
59+
logger.error(
60+
f"Failed to send email to {to_email} with template {template_id}: {e}"
61+
)
62+
raise

keep/api/utils/tenant_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def create_api_key(
1717
tenant_id: str,
1818
unique_api_key_id: str,
1919
is_system: bool,
20+
created_by: str,
2021
commit: bool = True,
2122
system_description: Optional[str] = None,
2223
) -> str:
@@ -54,6 +55,7 @@ def create_api_key(
5455
key_hash=hashed_api_key,
5556
is_system=is_system,
5657
system_description=system_description,
58+
created_by=created_by,
5759
)
5860
session.add(new_installation_api_key)
5961
if commit:
@@ -68,6 +70,7 @@ def create_api_key(
6870
def get_or_create_api_key(
6971
session: Session,
7072
tenant_id: str,
73+
created_by: str,
7174
unique_api_key_id: str,
7275
system_description: Optional[str] = None,
7376
) -> str:
@@ -98,6 +101,7 @@ def get_or_create_api_key(
98101
session,
99102
tenant_id,
100103
unique_api_key_id,
104+
created_by=created_by,
101105
is_system=True,
102106
system_description=system_description,
103107
)

keep/contextmanager/contextmanager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def api_key(self):
5656
session = next(get_session())
5757
self._api_key = get_or_create_api_key(
5858
session=session,
59+
created_by="system",
5960
tenant_id=self.tenant_id,
6061
unique_api_key_id="webhook",
6162
)

0 commit comments

Comments
 (0)