Coverage for chalicelib/core/collaborations/collaboration_slack.py: 38%
92 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:56 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:56 +0000
1from datetime import datetime
3from decouple import config
4from fastapi import HTTPException, status
6import schemas
7from chalicelib.core import webhook
8from chalicelib.core.collaborations.collaboration_base import BaseCollaboration
9import logging
11from chalicelib.utils import ssrf
12from chalicelib.utils.log import sanitize
14logger = logging.getLogger(__name__)
16class Slack(BaseCollaboration):
17 @classmethod
18 def add(cls, tenant_id, data: schemas.AddCollaborationSchema):
19 if webhook.exists_by_name(tenant_id=tenant_id, name=data.name, exclude_id=None, 19 ↛ 21line 19 didn't jump to line 21 because the condition on line 19 was never true
20 webhook_type=schemas.WebhookType.SLACK):
21 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.")
22 try:
23 hello = cls.say_hello(data.url)
24 except ValueError as e:
25 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
26 if hello: 26 ↛ 27line 26 didn't jump to line 27 because the condition on line 26 was never true
27 return webhook.add(tenant_id=tenant_id,
28 endpoint=data.url.unicode_string(),
29 webhook_type=schemas.WebhookType.SLACK,
30 name=data.name)
31 return None
33 @classmethod
34 def say_hello(cls, url):
35 r = ssrf.post_json(
36 endpoint=url,
37 json_data={
38 "attachments": [
39 {
40 "text": "Welcome to OpenReplay",
41 "ts": datetime.now().timestamp(),
42 }
43 ]
44 })
45 if r.status_code != 200: 45 ↛ 49line 45 didn't jump to line 49 because the condition on line 45 was always true
46 logger.error("slack integration failed")
47 logger.error(sanitize(r.text))
48 return False
49 return True
51 @classmethod
52 def send_raw(cls, tenant_id, webhook_id, body):
53 integration = cls.get_integration(tenant_id=tenant_id, integration_id=webhook_id)
54 if integration is None:
55 return {"errors": ["slack integration not found"]}
56 try:
57 r = ssrf.post_json(
58 endpoint=integration["endpoint"],
59 json_data=body,
60 timeout=5)
61 if r.status_code != 200:
62 logger.warning(f"!! issue sending slack raw; webhookId:{webhook_id} code:{r.status_code}")
63 logger.warning(sanitize(r.text))
64 return None
65 except requests.exceptions.Timeout:
66 logger.warning(f"!! Timeout sending slack raw webhookId:{webhook_id}")
67 return None
68 except Exception as e:
69 logger.warning(f"!! Issue sending slack raw webhookId:{webhook_id}")
70 logger.warning(sanitize(str(e)))
71 return None
72 return {"data": r.text}
74 @classmethod
75 def send_batch(cls, tenant_id, webhook_id, attachments):
76 integration = cls.get_integration(tenant_id=tenant_id, integration_id=webhook_id)
77 if integration is None:
78 return {"errors": ["slack integration not found"]}
79 logger.debug(f"====> sending slack batch notification: {len(attachments)}")
80 for i in range(0, len(attachments), 100):
81 r = ssrf.post_json(
82 endpoint=integration["endpoint"],
83 json_data={"attachments": attachments[i:i + 100]})
84 if r.status_code != 200:
85 logger.warning(f"!!!! something went wrong while sending slack batch; webhookId:{webhook_id} code:{r.status_code}")
86 logger.warning(sanitize(r.text))
88 @classmethod
89 def __share(cls, tenant_id, integration_id, attachement, extra=None):
90 if extra is None:
91 extra = {}
92 integration = cls.get_integration(tenant_id=tenant_id, integration_id=integration_id)
93 if integration is None:
94 return {"errors": ["slack integration not found"]}
95 attachement["ts"] = datetime.now().timestamp()
96 r = ssrf.post_json(endpoint=integration["endpoint"], json_data={"attachments": [attachement], **extra})
97 return r.text
99 @classmethod
100 def share_session(cls, tenant_id, project_id, session_id, user, comment, project_name=None, integration_id=None):
101 args = {"fallback": f"{user} has shared the below session!",
102 "pretext": f"{user} has shared the below session!",
103 "title": f"{config('SITE_URL')}/{project_id}/session/{session_id}",
104 "title_link": f"{config('SITE_URL')}/{project_id}/session/{session_id}",
105 "text": comment}
106 data = cls.__share(tenant_id, integration_id, attachement=args)
107 if "errors" in data:
108 return data
109 return {"data": data}
111 @classmethod
112 def share_error(cls, tenant_id, project_id, error_id, user, comment, project_name=None, integration_id=None):
113 args = {"fallback": f"{user} has shared the below error!",
114 "pretext": f"{user} has shared the below error!",
115 "title": f"{config('SITE_URL')}/{project_id}/errors/{error_id}",
116 "title_link": f"{config('SITE_URL')}/{project_id}/errors/{error_id}",
117 "text": comment}
118 data = cls.__share(tenant_id, integration_id, attachement=args)
119 if "errors" in data:
120 return data
121 return {"data": data}
123 @classmethod
124 def get_integration(cls, tenant_id, integration_id=None):
125 if integration_id is not None: 125 ↛ 129line 125 didn't jump to line 129 because the condition on line 125 was always true
126 return webhook.get_webhook(tenant_id=tenant_id, webhook_id=integration_id,
127 webhook_type=schemas.WebhookType.SLACK)
129 integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type=schemas.WebhookType.SLACK)
130 if integrations is None or len(integrations) == 0:
131 return None
132 return integrations[0]