teams-alerter 0.2.5__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teams-alerter
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Module pour envoyer des alertes Teams via Pub/Sub
5
5
  Author-email: Toki <t.bakotondrabe-ext@paris-turf.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "teams-alerter"
7
- version = "0.2.5"
7
+ version = "0.2.7"
8
8
  description = "Module pour envoyer des alertes Teams via Pub/Sub"
9
9
  authors = [{ name = "Toki", email = "t.bakotondrabe-ext@paris-turf.com" }]
10
10
  readme = "README.md"
@@ -0,0 +1,178 @@
1
+ import json
2
+ import traceback
3
+ import datetime
4
+
5
+ from google.cloud import pubsub_v1
6
+ from .utils import ErrorUtils, DateUtils, format_email_template, is_json
7
+
8
+
9
+ class TeamsAlerter:
10
+
11
+ def __init__(
12
+ self,
13
+ utils: ErrorUtils,
14
+ payload: None,
15
+ ):
16
+ self.utils = utils
17
+ self.payload = payload
18
+
19
+ @staticmethod
20
+ def handle_error(error: Exception, utils: ErrorUtils) -> None:
21
+ error_type = type(error).__name__
22
+ error_message = str(error)
23
+ error_traceback = traceback.format_exc()
24
+ utc_timestamp = DateUtils.get_str_utc_timestamp()
25
+ utc_timestamp_minus_5min = DateUtils.get_str_utc_timestamp_minus_5min()
26
+ utc_timestamp_plus_5min = DateUtils.get_str_utc_timestamp_plus_5min()
27
+ url_log = f"https://console.cloud.google.com/logs/query;cursorTimestamp={utc_timestamp};startTime={utc_timestamp_minus_5min};endTime={utc_timestamp_plus_5min}?referrer=search&hl=fr&inv=1&invt=Ab5Y1Q&project={utils['app_project_id']}"
28
+ # detail = f"Error type: {error_type}\nError message: {error_message}\nError traceback: {error_traceback}"
29
+ detail = {"type": error_type, "message": error_message, "traceback": error_traceback}
30
+ level = "ERROR"
31
+
32
+ teams_alerter = TeamsAlerter(utils=utils, payload={})
33
+ teams_alerter.format_payload(detail, level, url_log, utc_timestamp)
34
+ teams_alerter.publish_alert()
35
+
36
+ def publish_alert(self):
37
+ # Création d'un éditeur
38
+ publisher = pubsub_v1.PublisherClient()
39
+ topic_path = publisher.topic_path(self.utils["topic_project_id"], self.utils["topic_id"])
40
+
41
+ # Message à publier
42
+ data = json.dumps(self.payload).encode("utf-8")
43
+
44
+ # Publier le message
45
+ try:
46
+ publish_future = publisher.publish(topic_path, data)
47
+ publish_future.result()
48
+
49
+ except Exception as e:
50
+ self.utils["logger"](f"🟥Une erreur s'est produite lors de la publication du message : {e}")
51
+
52
+ def format_payload(self, detail, level, url_log, utc_timestamp):
53
+ app_list = {
54
+ "teams": [
55
+ "health_check_check_pg_wal_slot",
56
+ "health_check_check_meetings_ids",
57
+ "health_check_check_races_ids",
58
+ "health_check_check_runners_ids",
59
+ "health_check_check_processing_queue_ids",
60
+ ],
61
+ "email": [
62
+ "health_check_check_partants_data",
63
+ "health_check_check_horses_stats",
64
+ ],
65
+ }
66
+
67
+ # base payload
68
+ self.payload = {
69
+ # base info
70
+ "app_name": self.utils["app_name"],
71
+ "detail": detail,
72
+ "level": level,
73
+ "environment": self.utils["env"],
74
+ "url_log": url_log,
75
+ "timestamp": utc_timestamp,
76
+ # alerting info to complete
77
+ "alert_type": [], # teams, email
78
+ "teams_channel": "",
79
+ "teams_template": "",
80
+ "email_template_html": "",
81
+ }
82
+
83
+ if self.utils["app_name"] in app_list["email"]:
84
+ self.format_email_template()
85
+
86
+ if self.utils["app_name"] in app_list["teams"] or self.utils["app_name"] not in app_list["email"]:
87
+ self.format_teams_template()
88
+
89
+ def format_teams_template(self):
90
+ self.payload["alert_type"].append("teams")
91
+ self.payload["teams_channel"] = self.utils["teams_channel"]
92
+ self.payload["teams_template"] = "card"
93
+
94
+ def format_email_template(self):
95
+ self.payload["alert_type"].append("email")
96
+ self.payload["email_object"] = "Contrôle DATASTREAM"
97
+
98
+ if self.utils["app_name"] == "health_check_check_horses_stats":
99
+ if is_json(self.payload["detail"]["message"]):
100
+ # cette ligne plante si message n'est pas json
101
+ error_info_list = json.loads(self.payload["detail"]["message"])
102
+ table_data = [("ID CHEVAL", "CHAMP", "POSTGRES", "MONGO", "DIFFERENCE")]
103
+ for error_info in error_info_list["data"]:
104
+ table_data.append(
105
+ (
106
+ error_info["idCheval"],
107
+ error_info["champ"],
108
+ error_info["postgres"],
109
+ error_info["mongo"],
110
+ error_info["difference"],
111
+ )
112
+ )
113
+
114
+ email_object = "Contrôle DATASTREAM - Fiche cheval"
115
+ self.payload["email_object"] = email_object
116
+ email_messages = [
117
+ """
118
+ Bonjour, <br>
119
+ Veuillez trouver ci-dessous le tableau récapitulatif du contrôle effectué sur la fiche cheval dans Datastream.
120
+ """,
121
+ f"""
122
+ Env: <strong>{self.utils["env"]}</strong> <br>
123
+ Timestamp: {DateUtils.get_str_utc_timestamp()} <br>
124
+ Champs: <strong>formFigs et/ou totalPrize</strong>
125
+ """,
126
+ ]
127
+
128
+ self.payload["email_template_html"] = format_email_template(
129
+ email_object, email_messages, table_data, self.utils["app_name"]
130
+ )
131
+ else:
132
+ print(
133
+ "⚠ ERREUR INTERNE : error_message health_check_check_horses_stats n'est pas un JSON valide :",
134
+ self.payload["detail"]["message"],
135
+ )
136
+
137
+ elif self.utils["app_name"] == "health_check_check_partants_data":
138
+ if is_json(self.payload["detail"]["message"]):
139
+ error_info_list = json.loads(self.payload["detail"]["message"])
140
+ table_data = [("ID COURSE", "CHAMP", "POSTGRES", "MONGO", "DIFFERENCE")]
141
+ for error_info in error_info_list["data"]:
142
+ table_data.append(
143
+ (
144
+ error_info["idCourse"],
145
+ error_info["dateCourse"],
146
+ error_info["nbInitialRunners_postgres"],
147
+ error_info["nbInitialRunners_mongo"],
148
+ error_info["nbNonRunners_postgres"],
149
+ error_info["nbNonRunners_mongo"],
150
+ error_info["nonRunners_postgres"],
151
+ error_info["nonRunners_mongo"],
152
+ error_info["nbRunners_postgres"],
153
+ error_info["nbRunners_mongo"],
154
+ )
155
+ )
156
+ email_object = "Contrôle DATASTREAM - Partants (COURSES)"
157
+ self.payload["email_object"] = email_object
158
+
159
+ email_messages = [
160
+ f"""
161
+ Bonjour, <br>
162
+ Veuillez trouver ci-dessous le tableau récapitulatif du contrôle effectué sur les partants des courses du {datetime.date.today().strftime("%d/%m/%Y")} dans Datastream.
163
+ """,
164
+ f"""
165
+ Env: <strong>{self.utils["env"]}</strong> <br>
166
+ Timestamp: {DateUtils.get_str_utc_timestamp()} <br>
167
+ Table name: <strong>tb_course</strong>
168
+ """,
169
+ ]
170
+
171
+ self.payload["email_template_html"] = format_email_template(
172
+ email_object, email_messages, [], self.utils["app_name"]
173
+ )
174
+ else:
175
+ print(
176
+ "⚠ ERREUR INTERNE : error_message health_check_check_partants_data n'est pas un JSON valide :",
177
+ self.payload["detail"]["message"],
178
+ )
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import json
2
3
 
3
4
  from typing import TypedDict
4
5
  from google.cloud import logging
@@ -31,7 +32,7 @@ class DateUtils:
31
32
  return dt.strftime("%Y-%m-%dT%H:%M:%S") + ".000000000Z"
32
33
 
33
34
 
34
- def format_email_template(email_object, email_messages, table_data):
35
+ def format_email_template(email_object, email_messages, table_data, app_name):
35
36
  html = f"""
36
37
  <html>
37
38
  <head lang="fr">
@@ -61,7 +62,7 @@ def format_email_template(email_object, email_messages, table_data):
61
62
 
62
63
  <tr>
63
64
  <td style="padding:0 16px 24px 16px;">
64
- {build_html_table(table_data)}
65
+ {build_html_table(table_data, app_name)}
65
66
  </td>
66
67
  </tr>
67
68
 
@@ -90,20 +91,60 @@ def format_email_template(email_object, email_messages, table_data):
90
91
  return html
91
92
 
92
93
 
93
- def build_html_table(table_data: list):
94
+ def build_html_table(table_data: list, app_name: str):
95
+ if app_name == "health_check_check_horses_stats":
96
+ html_table = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse; font-family:Segoe UI, Arial, sans-serif;">'
97
+
98
+ # format header
99
+ html_table += "<tr>"
100
+ for header in table_data[0]:
101
+ html_table += f'<th style="padding:10px; font-size:14px; line-height:20px; color:#111827; border-bottom:1px solid #bbbbbb;">{header}</th>'
102
+ html_table += "</tr>"
103
+
104
+ # format rows
105
+ for row in table_data[1:]:
106
+ html_table += "<tr>"
107
+ for cell in row:
108
+ html_table += f'<td style="padding:10px; font-size:14px; line-height:20px; color:#111827; border-bottom:1px solid #bbbbbb;">{cell}</td>'
109
+ html_table += "</tr>"
110
+
111
+ html_table += "</table>"
112
+
113
+ return html_table
114
+
115
+ elif app_name == "health_check_check_partants_data":
116
+ return build_html_table_partants_course(table_data)
117
+
118
+
119
+ def build_html_table_partants_course(table_data: list):
94
120
  html_table = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse; font-family:Segoe UI, Arial, sans-serif;">'
95
121
 
96
122
  # format header
97
- html_table += "<tr>"
98
- for header in table_data[0]:
99
- html_table += f'<th style="padding:10px; font-size:14px; line-height:20px; color:#111827; border-bottom:1px solid #bbbbbb;">{header}</th>'
100
- html_table += "</tr>"
101
-
123
+ html_table += """
124
+ <tr>
125
+ <th align="center" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;" rowspan="2">ID Course</th>
126
+ <th align="center" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;" rowspan="2">Date Course</th>
127
+ <th align="center" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;" colspan="2">NB initial runners</th>
128
+ <th align="center" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;" colspan="2">NB non runners</th>
129
+ <th align="center" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;" colspan="2">Non runners</th>
130
+ <th align="center" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;" colspan="2">NB runners</th>
131
+ </tr>
132
+ <tr>
133
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Postgres</th>
134
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Mongo</th>
135
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Postgres</th>
136
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Mongo</th>
137
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Postgres</th>
138
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Mongo</th>
139
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Postgres</th>
140
+ <th align="left" style="padding:12px 10px; font-size:12px; line-height:16px; color:#374151; text-transform:uppercase; letter-spacing:.5px; border:2px solid #e5e7eb; background:#f9fafb;">Mongo</th>
141
+ </tr>
142
+ """
102
143
  # format rows
103
- for row in table_data[1:]:
144
+ for row in table_data:
104
145
  html_table += "<tr>"
105
146
  for cell in row:
106
- html_table += f'<td style="padding:10px; font-size:14px; line-height:20px; color:#111827; border-bottom:1px solid #bbbbbb;">{cell}</td>'
147
+ html_table += f'<td style="padding:10px; font-size:14px; line-height:20px; color:#111827; border:1px solid #bbbbbb;">{cell}</td>'
107
148
  html_table += "</tr>"
108
149
 
109
150
  html_table += "</table>"
@@ -122,3 +163,13 @@ def build_html_message(email_messages: list[str]):
122
163
  </tr>
123
164
  """
124
165
  return content
166
+
167
+
168
+ def is_json(value):
169
+ if not isinstance(value, str):
170
+ return False # ce n'est même pas une chaîne
171
+ try:
172
+ json.loads(value)
173
+ return True
174
+ except json.JSONDecodeError:
175
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teams-alerter
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Module pour envoyer des alertes Teams via Pub/Sub
5
5
  Author-email: Toki <t.bakotondrabe-ext@paris-turf.com>
6
6
  License: MIT
@@ -1,124 +0,0 @@
1
- import json
2
- import traceback
3
-
4
- from google.cloud import pubsub_v1
5
- from .utils import ErrorUtils, DateUtils, format_email_template
6
-
7
-
8
- class TeamsAlerter:
9
-
10
- def __init__(
11
- self,
12
- utils: ErrorUtils,
13
- payload: None,
14
- ):
15
- self.utils = utils
16
- self.payload = payload
17
-
18
- @staticmethod
19
- def handle_error(error: Exception, utils: ErrorUtils) -> None:
20
- error_type = type(error).__name__
21
- error_message = str(error)
22
- error_traceback = traceback.format_exc()
23
- utc_timestamp = DateUtils.get_str_utc_timestamp()
24
- utc_timestamp_minus_5min = DateUtils.get_str_utc_timestamp_minus_5min()
25
- utc_timestamp_plus_5min = DateUtils.get_str_utc_timestamp_plus_5min()
26
- url_log = f"https://console.cloud.google.com/logs/query;cursorTimestamp={utc_timestamp};startTime={utc_timestamp_minus_5min};endTime={utc_timestamp_plus_5min}?referrer=search&hl=fr&inv=1&invt=Ab5Y1Q&project={utils['app_project_id']}"
27
- # detail = f"Error type: {error_type}\nError message: {error_message}\nError traceback: {error_traceback}"
28
- detail = {"type": error_type, "message": error_message, "traceback": error_traceback}
29
- level = "ERROR"
30
-
31
- teams_alerter = TeamsAlerter(utils=utils, payload={})
32
- teams_alerter.format_payload(detail, level, url_log, utc_timestamp)
33
- teams_alerter.publish_alert()
34
-
35
- def publish_alert(self):
36
- # Création d'un éditeur
37
- publisher = pubsub_v1.PublisherClient()
38
- topic_path = publisher.topic_path(self.utils["topic_project_id"], self.utils["topic_id"])
39
-
40
- # Message à publier
41
- data = json.dumps(self.payload).encode("utf-8")
42
-
43
- # Publier le message
44
- try:
45
- publish_future = publisher.publish(topic_path, data)
46
- publish_future.result()
47
-
48
- except Exception as e:
49
- self.utils["logger"](f"🟥Une erreur s'est produite lors de la publication du message : {e}")
50
-
51
- def format_payload(self, detail, level, url_log, utc_timestamp):
52
- app_list = {
53
- "teams": [
54
- "health_check_check_pg_wal_slot",
55
- "health_check_check_meetings_ids",
56
- "health_check_check_races_ids",
57
- "health_check_check_partants_data",
58
- "health_check_check_runners_ids",
59
- ],
60
- "email": [
61
- "health_check_check_horses_stats",
62
- ],
63
- }
64
-
65
- # base payload
66
- self.payload = {
67
- # base info
68
- "app_name": self.utils["app_name"],
69
- "detail": detail,
70
- "level": level,
71
- "environment": self.utils["env"],
72
- "url_log": url_log,
73
- "timestamp": utc_timestamp,
74
- # alerting info to complete
75
- "alert_type": [], # teams, email
76
- "teams_channel": "",
77
- "teams_template": "",
78
- "email_template_html": "",
79
- }
80
-
81
- if self.utils["app_name"] in app_list["email"]:
82
- self.format_email_template()
83
-
84
- if self.utils["app_name"] in app_list["teams"] or self.utils["app_name"] not in app_list["email"]:
85
- self.format_teams_template()
86
-
87
- def format_teams_template(self):
88
- self.payload["alert_type"].append("teams")
89
- self.payload["teams_channel"] = self.utils["teams_channel"]
90
- self.payload["teams_template"] = "card"
91
-
92
- def format_email_template(self):
93
- self.payload["alert_type"].append("email")
94
- self.payload["email_object"] = "Contrôle DATASTREAM"
95
-
96
- if self.utils["app_name"] == "health_check_check_horses_stats":
97
- error_info_list = json.loads(self.payload["detail"]["message"])
98
- table_data = [("ID CHEVAL", "CHAMP", "POSTGRES", "MONGO", "DIFFERENCE")]
99
- for error_info in error_info_list["data"]:
100
- table_data.append(
101
- (
102
- error_info["idCheval"],
103
- error_info["champ"],
104
- error_info["postgres"],
105
- error_info["mongo"],
106
- error_info["difference"],
107
- )
108
- )
109
-
110
- email_object = "Contrôle DATASTREAM - Fiche cheval"
111
- self.payload["email_object"] = email_object
112
- email_messages = [
113
- """
114
- Bonjour, <br>
115
- Veuillez trouver ci-dessous le tableau récapitulatif du contrôle effectué sur la fiche cheval dans Datastream.
116
- """,
117
- f"""
118
- Env: <strong>{self.utils["env"]}</strong> <br>
119
- Timestamp: {DateUtils.get_str_utc_timestamp()} <br>
120
- Champs: <strong>formFigs et/ou totalPrize</strong>
121
- """,
122
- ]
123
-
124
- self.payload["email_template_html"] = format_email_template(email_object, email_messages, table_data)
File without changes
File without changes