From 2d21d5383c5edb7cdf27ffc66e94fe3fcccf7c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Brossard?= Date: Fri, 17 Nov 2023 15:23:22 +0100 Subject: [PATCH 1/4] feat: add teams notification --- app/leek/api/channels/pipeline.py | 4 +++ app/leek/api/channels/teams.py | 42 ++++++++++++++++++++++++ app/leek/api/schemas/application.py | 3 +- app/leek/api/utils.py | 24 ++++++++++---- app/web/src/containers/apps/Triggers.tsx | 32 ++++++++++++++++-- 5 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 app/leek/api/channels/teams.py diff --git a/app/leek/api/channels/pipeline.py b/app/leek/api/channels/pipeline.py index de99573..9be83ea 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]]): @@ -51,3 +53,5 @@ def notify(app: Application, env, events: List[Union[Task, Worker]]): # Finally: notify if trigger.type == Channels.SLACK: send_slack(app.app_name, event, trigger.slack_wh_url, extra={"note": note}) + elif trigger.type == Channels.TEAMS: + send_teams(app.app_name, event, trigger.teams_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..055e23a --- /dev/null +++ b/app/leek/api/channels/teams.py @@ -0,0 +1,42 @@ +import logging +from typing import Union +import pymsteams + +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): + teams_message = pymsteams.connectorcard(wh_url) + + teams_message.title(f"Task: {event.name}") + teams_message.color(get_color(event.state)) + teams_message.addLinkButton("View task", f"{settings.LEEK_WEB_URL}/task?app={app_name}&uuid={event.uuid}") + + section = pymsteams.cardsection() + section.addFact("Application", app_name) + section.addFact("Environment", event.app_env) + section.addFact("Task worker", event.worker) + section.addFact("Task state", event.state) + section.addFact("Task uuid", event.uuid) + + if extra.get("note"): + section.addFact("Note", extra.get("note")) + + teams_message.addSection(section) + teams_message.send() diff --git a/app/leek/api/schemas/application.py b/app/leek/api/schemas/application.py index d0628ff..23e1b0b 100644 --- a/app/leek/api/schemas/application.py +++ b/app/leek/api/schemas/application.py @@ -8,8 +8,9 @@ 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"), + "type": Or("slack", "teams"), "slack_wh_url": And(str, len), + "teams_wh_url": And(str, len), }) ApplicationSchema = Schema( diff --git a/app/leek/api/utils.py b/app/leek/api/utils.py index a5f37af..81412aa 100644 --- a/app/leek/api/utils.py +++ b/app/leek/api/utils.py @@ -2,6 +2,7 @@ import logging import random import string +import pymsteams import requests @@ -18,14 +19,14 @@ def generate_app_key(length=48): def init_trigger(tr, app_name): trigger = FanoutTrigger(**tr) + 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.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" try: response = requests.post( url=trigger.slack_wh_url, @@ -37,6 +38,15 @@ def init_trigger(tr, app_name): return False except requests.exceptions.HTTPError as e: return False + if trigger.teams_wh_url: + try: + my_teams_message = pymsteams.connectorcard(trigger.teams_wh_url) + my_teams_message.text(text) + my_teams_message.send() + except Exception as e: + logger.error(f"Request to teams returned an error: {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..94c8ffa 100644 --- a/app/web/src/containers/apps/Triggers.tsx +++ b/app/web/src/containers/apps/Triggers.tsx @@ -62,6 +62,8 @@ const Triggers = (props) => { const [patternType, setPatternType] = useState("all"); const [loading, setLoading] = useState(false); const [triggerId, setTriggerId] = useState(); + const [selectValue, setSelectValue] = useState("slack"); + useEffect(() => {}, []); @@ -82,6 +84,10 @@ const Triggers = (props) => { }); } + function onSelectChange(event) { + setSelectValue(event.target.value); + } + function doEditTrigger(trigger) { delete trigger.patterns; setLoading(true); @@ -136,15 +142,19 @@ const Triggers = (props) => { const formItems = ( <> - + - { prefix={} placeholder="Webhook URL" /> - + )} + + { selectValue === 'teams' && ( + } + placeholder="Webhook URL" + /> + )} + - { selectValue === 'slack' && ( - } - placeholder="Webhook URL" - /> - )} - - { selectValue === 'teams' && ( } placeholder="Webhook URL" /> - )} +