fix: limit rocket notification size
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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==
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user