fix: limit rocket notification size

This commit is contained in:
2026-07-24 10:36:18 +02:00
parent 7c108154df
commit b10b82a141
6 changed files with 62 additions and 11 deletions
+3 -1
View File
@@ -59,6 +59,7 @@ timeout = 30
[notification] [notification]
max_tickets = 50 max_tickets = 50
max_message_chars = 3500
ticket_url_template = {odoo_url}/web#id={id}&model={model}&view_type=form ticket_url_template = {odoo_url}/web#id={id}&model={model}&view_type=form
``` ```
@@ -97,7 +98,7 @@ La cle API est utilisee comme mot de passe d'API Odoo pour authentifier `usernam
`odoo.limit = 0` force la recuperation de tous les tickets. `odoo.order = id asc` stabilise la comparaison avec l'etat local. `odoo.limit = 0` force la recuperation de tous les tickets. `odoo.order = id asc` stabilise la comparaison avec l'etat local.
`notification.max_tickets` limite le nombre de tickets affiches dans Rocket.Chat. `notification.ticket_url_template` controle le lien d'ouverture du ticket ; les variables disponibles sont `{odoo_url}`, `{id}` et `{model}`. `notification.max_tickets` limite le nombre de tickets affiches dans Rocket.Chat. `notification.max_message_chars` limite la taille totale du message pour eviter les rejets Rocket.Chat sur les gros audits. `notification.ticket_url_template` controle le lien d'ouverture du ticket ; les variables disponibles sont `{odoo_url}`, `{id}` et `{model}`.
## Protocole Odoo ## Protocole Odoo
@@ -138,6 +139,7 @@ Lecture des tickets :
Quand des tickets changent, Rocket.Chat recoit un message avec : Quand des tickets changent, Rocket.Chat recoit un message avec :
- une ligne compacte par ticket affiche, limitee par `notification.max_tickets` ; - une ligne compacte par ticket affiche, limitee par `notification.max_tickets` ;
- une taille totale limitee par `notification.max_message_chars` ;
- l'ID, le sujet, la personne assignee et un lien d'ouverture direct dans Odoo ; - l'ID, le sujet, la personne assignee et un lien d'ouverture direct dans Odoo ;
- une ligne de resume si tous les tickets modifies ne sont pas affiches. - une ligne de resume si tous les tickets modifies ne sont pas affiches.
+8
View File
@@ -135,3 +135,11 @@ Ce fichier consigne les interventions Codex sur le projet afin de garder une tra
- Ajout explicite de `order = id asc` pour stabiliser la liste des tickets comparee avec l'etat local. - Ajout explicite de `order = id asc` pour stabiliser la liste des tickets comparee avec l'etat local.
- Ajout de logs d'audit indiquant le nombre de tickets recuperes et le nombre de tickets modifies detectes. - Ajout de logs d'audit indiquant le nombre de tickets recuperes et le nombre de tickets modifies detectes.
- Incrementation de la version WAPT de `0.1-13` a `0.1-14`. - Incrementation de la version WAPT de `0.1-13` a `0.1-14`.
### Limitation taille Rocket.Chat
- Analyse d'une erreur Rocket.Chat `400 Bad Request` sur VM avec 144 tickets modifies detectes.
- Ajout de `notification.max_message_chars`, valeur par defaut `3500`, pour limiter la taille totale du message envoye.
- Conservation du resume indiquant combien de tickets supplementaires ne sont pas affiches.
- Ajout du corps de reponse Rocket.Chat dans les logs en cas d'erreur HTTP.
- Incrementation de la version WAPT de `0.1-15` a `0.1-16`.
+4 -4
View File
@@ -1,5 +1,5 @@
package : comi-odoo-ticketing package : comi-odoo-ticketing
version : 0.1-14 version : 0.1-16
architecture : all architecture : all
section : base section : base
priority : optional priority : optional
@@ -29,7 +29,7 @@ editor :
keywords : keywords :
licence : licence :
homepage : homepage :
package_uuid : 94cab12e-61fb-4a49-fcd2-c1d160d18b22 package_uuid : 26501dce-4f1c-c379-c701-370a6908dd53
valid_from : valid_from :
valid_until : valid_until :
forced_install_on : forced_install_on :
@@ -39,6 +39,6 @@ max_os_version :
icon_sha256sum : icon_sha256sum :
signer : pcosson_key signer : pcosson_key
signer_fingerprint: a25582410cf03bad179a60c189f459a0b03821c92c0cedf209e82448a66a9b4e signer_fingerprint: a25582410cf03bad179a60c189f459a0b03821c92c0cedf209e82448a66a9b4e
signature_date : 2026-07-24T07:48:26.000000 signature_date : 2026-07-24T08:27:24.000000
signed_attributes : package,version,architecture,section,priority,name,categories,maintainer,description,depends,conflicts,maturity,locale,target_os,min_wapt_version,sources,installed_size,impacted_process,description_fr,description_pl,description_de,description_es,description_pt,description_it,description_nl,description_ru,audit_schedule,editor,keywords,licence,homepage,package_uuid,valid_from,valid_until,forced_install_on,changelog,min_os_version,max_os_version,icon_sha256sum,signer,signer_fingerprint,signature_date,signed_attributes signed_attributes : package,version,architecture,section,priority,name,categories,maintainer,description,depends,conflicts,maturity,locale,target_os,min_wapt_version,sources,installed_size,impacted_process,description_fr,description_pl,description_de,description_es,description_pt,description_it,description_nl,description_ru,audit_schedule,editor,keywords,licence,homepage,package_uuid,valid_from,valid_until,forced_install_on,changelog,min_os_version,max_os_version,icon_sha256sum,signer,signer_fingerprint,signature_date,signed_attributes
signature : FGra+/RipOk5jmR4CkBmu4s9DQkzJ3u8bGhssL3zpRgzynMdnfNDSgVkBIM5XoQsqb+GluNnvqzGT19jvhOr1OVxF4Fk6ErMK/oXezLe/lX2vezTfRGoSUzVG+KPZrzqcuM0Q0g6gu2sctASMw0RuMMdMVgXWrZt/zVu+wS6P6O5MpCoOVRLMoV22jXAuXSz888rwBmSOyVU860Un1wPm4zin+gaJgvsy3GZQSSMhJBR8CGAj8QmeL6cjJrspJ0LDiE8m4UJXHJvlAsRIGxs9Y928L6x2Tg0pl6j02wNnZTKeh3mAM5KZRy5UJR8fbqXbBDuJJR7R1gux2WTw7mEeQ== signature : HUbxaUjEjN7gWHV0cfLtO0ofdC4NYqFZp4+0+Lq2bv6f7wD+U3cNXIOj1tvnrXZEs9RKWKcsNVgmq7bwWz0wpWCOTT2TDZ/PI0srd6NEq7jkBg4l6AE6ssjVjr/SRDFKB2bPEcQI7e0Mgj7uG6ho2eCvZFV2txH5pW4vV78GTZffTxvmZddi6PVKXMk4SBmWB8FFNCAPPvS50NVvmPQio7JHgRy4EBMWSSjcTLMcDAcHvcWp8BYGXy3csbpIxKfe8RIIkMLPT7cWQiIJRMzWurXanGXwXD8YF1lSwrXNmz5sOCmJ5MII9ljA/irbp3OsCvO9T5zgJkRWpw0wD/KGRw==
+22 -6
View File
@@ -14,6 +14,7 @@ STATE_FILE = "tickets_state.json"
DEFAULT_TIMEOUT = 30 DEFAULT_TIMEOUT = 30
DEFAULT_TICKET_FIELDS = ["id", "name", "stage_id", "partner_id", "user_id", "write_date"] DEFAULT_TICKET_FIELDS = ["id", "name", "stage_id", "partner_id", "user_id", "write_date"]
DEFAULT_MAX_NOTIFIED_TICKETS = 50 DEFAULT_MAX_NOTIFIED_TICKETS = 50
DEFAULT_MAX_MESSAGE_CHARS = 3500
DEFAULT_TICKET_URL_TEMPLATE = "{odoo_url}/web#id={id}&model={model}&view_type=form" DEFAULT_TICKET_URL_TEMPLATE = "{odoo_url}/web#id={id}&model={model}&view_type=form"
DEFAULT_ODOO_ORDER = "id asc" DEFAULT_ODOO_ORDER = "id asc"
@@ -44,6 +45,7 @@ def audit():
tickets_updated, tickets_updated,
previous_tickets_state, previous_tickets_state,
get_max_notified_tickets(conf_wapt), get_max_notified_tickets(conf_wapt),
get_max_message_chars(conf_wapt),
get_ticket_url_template(conf_wapt), get_ticket_url_template(conf_wapt),
conf_wapt.get("odoo", "url").rstrip("/"), conf_wapt.get("odoo", "url").rstrip("/"),
conf_wapt.get("odoo", "model", fallback="helpdesk.ticket"), conf_wapt.get("odoo", "model", fallback="helpdesk.ticket"),
@@ -117,6 +119,10 @@ def get_max_notified_tickets(conf_wapt):
return conf_wapt.getint("notification", "max_tickets", fallback=DEFAULT_MAX_NOTIFIED_TICKETS) return conf_wapt.getint("notification", "max_tickets", fallback=DEFAULT_MAX_NOTIFIED_TICKETS)
def get_max_message_chars(conf_wapt):
return conf_wapt.getint("notification", "max_message_chars", fallback=DEFAULT_MAX_MESSAGE_CHARS)
def get_ticket_url_template(conf_wapt): def get_ticket_url_template(conf_wapt):
return conf_wapt.get("notification", "ticket_url_template", fallback=DEFAULT_TICKET_URL_TEMPLATE) return conf_wapt.get("notification", "ticket_url_template", fallback=DEFAULT_TICKET_URL_TEMPLATE)
@@ -297,20 +303,28 @@ def format_detailed_ticket_line(ticket, previous_tickets_state):
return "- " + " | ".join(parts) return "- " + " | ".join(parts)
def build_notification_message(tickets_updated, previous_tickets_state, max_tickets, ticket_url_template, odoo_url, model): def build_notification_message(tickets_updated, previous_tickets_state, max_tickets, max_message_chars, ticket_url_template, odoo_url, model):
if not tickets_updated: if not tickets_updated:
return "Aucun ticket n'a ete mis a jour depuis la derniere verification." return "Aucun ticket n'a ete mis a jour depuis la derniere verification."
displayed_tickets = tickets_updated[:max_tickets] displayed_tickets = tickets_updated[:max_tickets]
hidden_count = len(tickets_updated) - len(displayed_tickets)
lines = [ lines = [
"Les tickets suivants ont ete mis a jour depuis la derniere verification :", "Les tickets suivants ont ete mis a jour depuis la derniere verification :",
] ]
lines.extend([ displayed_count = 0
format_ticket_line(ticket, ticket_url_template, odoo_url, model)
for ticket in displayed_tickets
])
for ticket in displayed_tickets:
ticket_line = format_ticket_line(ticket, ticket_url_template, odoo_url, model)
candidate_lines = lines + [ticket_line]
candidate_message = "\n".join(candidate_lines)
if max_message_chars > 0 and len(candidate_message) > max_message_chars:
break
lines.append(ticket_line)
displayed_count += 1
hidden_count = len(tickets_updated) - displayed_count
if hidden_count > 0: if hidden_count > 0:
lines.extend([ lines.extend([
"", "",
@@ -346,5 +360,7 @@ def send_to_rocket(message_text, attachments=None, conf_wapt=None):
headers={"Content-Type": "application/json"}, headers={"Content-Type": "application/json"},
timeout=get_http_timeout(conf_wapt), timeout=get_http_timeout(conf_wapt),
) )
if response.status_code >= 400:
print("Erreur Rocket.Chat : %s" % response.text)
response.raise_for_status() response.raise_for_status()
print("Message envoye avec succes.") print("Message envoye avec succes.")
+24
View File
@@ -43,6 +43,8 @@ def make_config():
config.set("rocket", "webhook_url", "https://rocket.example.test/webhook-test") config.set("rocket", "webhook_url", "https://rocket.example.test/webhook-test")
config.add_section("http") config.add_section("http")
config.set("http", "timeout", "5") config.set("http", "timeout", "5")
config.add_section("notification")
config.set("notification", "max_message_chars", "3500")
return config return config
@@ -98,6 +100,7 @@ class TicketingStateTest(unittest.TestCase):
tickets, tickets,
previous_state, previous_state,
1, 1,
3500,
"{odoo_url}/web#id={id}&model={model}&view_type=form", "{odoo_url}/web#id={id}&model={model}&view_type=form",
"https://odoo.example.test", "https://odoo.example.test",
"helpdesk.ticket", "helpdesk.ticket",
@@ -113,6 +116,26 @@ class TicketingStateTest(unittest.TestCase):
self.assertIn("1 ticket(s) supplementaire(s) non affiche(s) sur 2 au total", message) self.assertIn("1 ticket(s) supplementaire(s) non affiche(s) sur 2 au total", message)
self.assertNotIn("Nouveau ticket", message) self.assertNotIn("Nouveau ticket", message)
def test_build_notification_message_is_limited_by_message_size(self):
tickets = [
{"id": 1, "name": "Premier ticket avec sujet assez long", "user_id": False},
{"id": 2, "name": "Deuxieme ticket avec sujet assez long", "user_id": False},
]
message = ticketing.build_notification_message(
tickets,
{},
50,
260,
"{odoo_url}/web#id={id}&model={model}&view_type=form",
"https://odoo.example.test",
"helpdesk.ticket",
)
self.assertIn("1 : Sujet : Premier ticket", message)
self.assertNotIn("2 : Sujet : Deuxieme ticket", message)
self.assertIn("1 ticket(s) supplementaire(s) non affiche(s) sur 2 au total", message)
def test_format_ticket_line_uses_personne_when_no_assignee(self): def test_format_ticket_line_uses_personne_when_no_assignee(self):
line = ticketing.format_ticket_line( line = ticketing.format_ticket_line(
{"id": 2, "name": "Nouveau ticket", "user_id": False}, {"id": 2, "name": "Nouveau ticket", "user_id": False},
@@ -192,6 +215,7 @@ class TicketingStateTest(unittest.TestCase):
@patch("ticketing_setup.requests.post") @patch("ticketing_setup.requests.post")
def test_send_to_rocket_raises_for_http_errors(self, post): def test_send_to_rocket_raises_for_http_errors(self, post):
response = Mock() response = Mock()
response.status_code = 200
post.return_value = response post.return_value = response
ticketing.send_to_rocket("message de test", conf_wapt=make_config()) ticketing.send_to_rocket("message de test", conf_wapt=make_config())
+1
View File
@@ -16,4 +16,5 @@ timeout = 30
[notification] [notification]
max_tickets = 50 max_tickets = 50
max_message_chars = 3500
ticket_url_template = {odoo_url}/web#id={id}&model={model}&view_type=form ticket_url_template = {odoo_url}/web#id={id}&model={model}&view_type=form