diff --git a/app/leek/api/channels/pipeline.py b/app/leek/api/channels/pipeline.py index de99573..5b28d10 100644 --- a/app/leek/api/channels/pipeline.py +++ b/app/leek/api/channels/pipeline.py @@ -3,10 +3,12 @@ from leek.api.db.store import Task, Worker, Application, STATES_SUCCESS, EventKind from .slack import send_slack +from .teams import send_teams class Channels: SLACK = "slack" + TEAMS = "teams" def notify(app: Application, env, events: List[Union[Task, Worker]]): @@ -50,4 +52,6 @@ def notify(app: Application, env, events: List[Union[Task, Worker]]): note = f"Runtime upper bound exceeded: `{runtime} seconds`" # Finally: notify if trigger.type == Channels.SLACK: - send_slack(app.app_name, event, trigger.slack_wh_url, extra={"note": note}) + send_slack(app.app_name, event, trigger.wh_url, extra={"note": note}) + elif trigger.type == Channels.TEAMS: + send_teams(app.app_name, event, trigger.wh_url, extra={"note": note}) diff --git a/app/leek/api/channels/teams.py b/app/leek/api/channels/teams.py new file mode 100644 index 0000000..af58bea --- /dev/null +++ b/app/leek/api/channels/teams.py @@ -0,0 +1,73 @@ +import logging +from typing import Union + +import requests + +from leek.api.db.store import Task, Worker, STATES_SUCCESS, STATES_EXCEPTION, STATES_UNREADY +from leek.api.conf import settings + +logger = logging.getLogger(__name__) + + +def get_color(state): + if state in STATES_EXCEPTION: + return "f44336" + elif state in STATES_SUCCESS: + return "66cc99" + elif state in STATES_UNREADY: + return "36C5F0" + else: + return "f1c232" + + +def send_teams(app_name: str, event: Union[Task, Worker], wh_url: str, extra: dict): + + body = { + "@type": "MessageCard", + "@context": "http://schema.org/extensions", + "themeColor": get_color(event.state), + "summary": f"Task: {event.name}", + "sections": [{ + "activityTitle": f"Task: {event.name}", + "activitySubtitle": f"Application: {app_name}", + "activityImage": "https://raw.githubusercontent.com/kodless/leek/master/doc/static/img/logo.png", + "facts": [{ + "name": "Environment", + "value": event.app_env + }, { + "name": "Task worker", + "value": event.worker + }, { + "name": "Task state", + "value": event.state + }, { + "name": "Task uuid", + "value": event.uuid + }], + "markdown": True + }], + "potentialAction": [{ + "@type": "OpenUri", + "name": "View task", + "targets": [{ + "os": "default", + "uri": f"{settings.LEEK_WEB_URL}/task?app={app_name}&uuid={event.uuid}" + }] + }] + } + + if extra.get("note"): + body["sections"][0]["facts"].append({ + "name": "Note", + "value": extra.get("note") + }) + + try: + requests.post( + wh_url, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + json=body + ).raise_for_status() + except Exception as e: + logger.error(f"Request to teams returned an error: {e}") + diff --git a/app/leek/api/db/store.py b/app/leek/api/db/store.py index 4f63fab..3937c7e 100644 --- a/app/leek/api/db/store.py +++ b/app/leek/api/db/store.py @@ -151,7 +151,7 @@ class Task(EV): class FanoutTrigger: id: str enabled: bool - slack_wh_url: str + wh_url: str type: str = "slack" states: List = field(default_factory=lambda: []) envs: List = field(default_factory=lambda: []) diff --git a/app/leek/api/schemas/application.py b/app/leek/api/schemas/application.py index d0628ff..b11bede 100644 --- a/app/leek/api/schemas/application.py +++ b/app/leek/api/schemas/application.py @@ -8,8 +8,8 @@ Optional("envs", default=[]): [str], Optional("runtime_upper_bound"): And(Use(float), lambda n: 0.000000000001 <= n <= 1000), Optional(Or("exclude", "include", only_one=True)): [str], - "type": Or("slack"), - "slack_wh_url": And(str, len), + "type": Or("slack", "teams"), + "wh_url": And(str, len), }) ApplicationSchema = Schema( diff --git a/app/leek/api/utils.py b/app/leek/api/utils.py index a5f37af..3a8a66c 100644 --- a/app/leek/api/utils.py +++ b/app/leek/api/utils.py @@ -18,25 +18,29 @@ def generate_app_key(length=48): def init_trigger(tr, app_name): trigger = FanoutTrigger(**tr) - if trigger.slack_wh_url: - text = f"Leek trigger configured for application `{app_name}`:\n" \ - f"- *enabled*: {trigger.enabled}\n" \ - f"- *envs*: {trigger.envs}\n" \ - f"- *states*: {trigger.states}\n" \ - f"- *exclude*: {trigger.exclude}\n" \ - f"- *include*: {trigger.include}\n" \ - f"- *runtime upper bound*: {trigger.runtime_upper_bound} seconds" + text = f"Leek trigger configured for application `{app_name}`:\n" \ + f"- *enabled*: {trigger.enabled}\n" \ + f"- *envs*: {trigger.envs}\n" \ + f"- *states*: {trigger.states}\n" \ + f"- *exclude*: {trigger.exclude}\n" \ + f"- *include*: {trigger.include}\n" \ + f"- *runtime upper bound*: {trigger.runtime_upper_bound} seconds" + + if trigger.wh_url: try: response = requests.post( - url=trigger.slack_wh_url, + url=trigger.wh_url, json={"text": text}, headers={"Content-Type": "application/json"} ) response.raise_for_status() # Raises a HTTPError if the status is 4xx, 5xxx - except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: + print(e) return False except requests.exceptions.HTTPError as e: + print(e) return False + return True diff --git a/app/web/src/containers/apps/Triggers.tsx b/app/web/src/containers/apps/Triggers.tsx index 319633b..a9db908 100644 --- a/app/web/src/containers/apps/Triggers.tsx +++ b/app/web/src/containers/apps/Triggers.tsx @@ -24,6 +24,7 @@ import { DeploymentUnitOutlined, SlackOutlined, BellOutlined, + WindowsOutlined } from "@ant-design/icons"; import TriggerDataColumns from "../../components/data/TriggerData"; @@ -63,6 +64,7 @@ const Triggers = (props) => { const [loading, setLoading] = useState(false); const [triggerId, setTriggerId] = useState(); + useEffect(() => {}, []); function doAddTrigger(trigger) { @@ -141,17 +143,17 @@ const Triggers = (props) => { Slack + { {record.slack_wh_url}} + description={{record.wh_url}} />