Merge pull request 'Auto-commit local changes before build (2026-01-08 14:18:20)' (#66) from v20260108-31-inbox-empty-body-attachment-render into main
Reviewed-on: #66
This commit is contained in:
commit
861767950d
@ -1 +1 @@
|
||||
v20260108-30-customer-delete-ticket-remark-scopes
|
||||
v20260108-31-inbox-empty-body-attachment-render
|
||||
|
||||
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from email import policy
|
||||
from email.parser import BytesParser
|
||||
from email.utils import parseaddr
|
||||
import re
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
|
||||
@ -125,3 +126,42 @@ def extract_best_html_from_eml(
|
||||
return None
|
||||
_fn, html_text = items[0]
|
||||
return html_text or None
|
||||
|
||||
|
||||
def is_effectively_blank_html(value: str | None) -> bool:
|
||||
"""Return True when an HTML body is effectively empty.
|
||||
|
||||
Some sources produce Graph bodies that are non-empty strings but contain only
|
||||
an empty HTML skeleton (e.g. <html><body></body></html>) or whitespace.
|
||||
In those cases we want to treat the body as empty so we can fall back to an
|
||||
HTML report attachment stored in the EML.
|
||||
"""
|
||||
if value is None:
|
||||
return True
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
|
||||
raw = value.strip()
|
||||
if raw == "":
|
||||
return True
|
||||
|
||||
# Fast path: if we clearly have content-bearing elements, it is not blank.
|
||||
# (This avoids false positives for report-like HTML.)
|
||||
if re.search(r"<(table|img|svg|pre|ul|ol|li|iframe|object|embed)\b", raw, re.IGNORECASE):
|
||||
return False
|
||||
|
||||
# Try to isolate the body content; if no body tag is present, evaluate the full string.
|
||||
m = re.search(r"<body\b[^>]*>(.*?)</body>", raw, re.IGNORECASE | re.DOTALL)
|
||||
body = m.group(1) if m else raw
|
||||
|
||||
# Remove comments, scripts, and styles.
|
||||
body = re.sub(r"<!--.*?-->", "", body, flags=re.DOTALL)
|
||||
body = re.sub(r"<script\b[^>]*>.*?</script>", "", body, flags=re.IGNORECASE | re.DOTALL)
|
||||
body = re.sub(r"<style\b[^>]*>.*?</style>", "", body, flags=re.IGNORECASE | re.DOTALL)
|
||||
|
||||
# Strip tags and common non-breaking whitespace entities.
|
||||
text = re.sub(r"<[^>]+>", "", body)
|
||||
text = text.replace(" ", " ").replace("\xa0", " ")
|
||||
text = re.sub(r"\s+", "", text)
|
||||
|
||||
return text == ""
|
||||
|
||||
@ -13,7 +13,7 @@ from sqlalchemy import func
|
||||
from . import db
|
||||
from .models import MailMessage, SystemSettings, Job, JobRun
|
||||
from .parsers import parse_mail_message
|
||||
from .email_utils import normalize_from_address, extract_best_html_from_eml
|
||||
from .email_utils import normalize_from_address, extract_best_html_from_eml, is_effectively_blank_html
|
||||
from .job_matching import find_matching_job
|
||||
|
||||
|
||||
@ -230,13 +230,13 @@ def _store_messages(settings: SystemSettings, messages):
|
||||
# Some systems send empty bodies and put the actual report in an HTML attachment.
|
||||
# Graph may still return a body that only contains whitespace/newlines; treat that
|
||||
# as empty so we can fall back to the attachment.
|
||||
def _is_blank(s):
|
||||
def _is_blank_text(s):
|
||||
return s is None or (isinstance(s, str) and s.strip() == "")
|
||||
|
||||
# If we have raw EML bytes and no meaningful body content, extract the first
|
||||
# HTML attachment and use it as the HTML body so parsers and the inbox preview
|
||||
# can work.
|
||||
if _is_blank(mail.html_body) and _is_blank(mail.text_body) and mail.eml_blob:
|
||||
if is_effectively_blank_html(mail.html_body) and _is_blank_text(mail.text_body) and mail.eml_blob:
|
||||
attachment_html = extract_best_html_from_eml(mail.eml_blob)
|
||||
if attachment_html:
|
||||
mail.html_body = attachment_html
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from .routes_shared import * # noqa: F401,F403
|
||||
from .routes_shared import _format_datetime, _log_admin_event, _send_mail_message_eml_download
|
||||
|
||||
from ..email_utils import extract_best_html_from_eml
|
||||
from ..email_utils import extract_best_html_from_eml, is_effectively_blank_html
|
||||
|
||||
import time
|
||||
|
||||
@ -113,7 +113,7 @@ def inbox_message_detail(message_id: int):
|
||||
),
|
||||
}
|
||||
|
||||
def _is_blank(s):
|
||||
def _is_blank_text(s):
|
||||
return s is None or (isinstance(s, str) and s.strip() == "")
|
||||
|
||||
html_body = getattr(msg, "html_body", None)
|
||||
@ -121,14 +121,14 @@ def inbox_message_detail(message_id: int):
|
||||
|
||||
# For legacy messages: if the Graph body is empty/whitespace but the real report
|
||||
# is an HTML attachment in the stored EML, extract and render it.
|
||||
if _is_blank(html_body) and _is_blank(text_body) and getattr(msg, "eml_blob", None):
|
||||
if is_effectively_blank_html(html_body) and _is_blank_text(text_body) and getattr(msg, "eml_blob", None):
|
||||
extracted = extract_best_html_from_eml(getattr(msg, "eml_blob", None))
|
||||
if extracted:
|
||||
html_body = extracted
|
||||
|
||||
if not _is_blank(html_body):
|
||||
if not is_effectively_blank_html(html_body):
|
||||
body_html = html_body
|
||||
elif not _is_blank(text_body):
|
||||
elif not _is_blank_text(text_body):
|
||||
escaped = (
|
||||
text_body.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
|
||||
@ -31,13 +31,20 @@
|
||||
|
||||
---
|
||||
|
||||
## v20260108-29-customer-delete-ticket-remark-scopes
|
||||
## v20260108-30-customer-delete-ticket-remark-scopes
|
||||
|
||||
- Updated customer deletion logic to allow removal of customers that have linked tickets or remarks.
|
||||
- Added explicit cleanup of related TicketScope and RemarkScope records before deleting the customer.
|
||||
- Ensured jobs linked to the customer are safely unassigned to prevent foreign key constraint errors.
|
||||
- Prevented deletion failures caused by existing ticket and remark relationships.
|
||||
|
||||
---
|
||||
|
||||
## v20260108-31-inbox-empty-body-attachment-render
|
||||
- Detect “effectively empty” HTML bodies (e.g. empty HTML skeleton from Graph) and treat them as blank.
|
||||
- Inbox import: when the Graph body is effectively empty and an EML is stored, extract the first HTML attachment and store it as html_body.
|
||||
- Inbox message detail: for already-stored messages with effectively empty bodies, dynamically fall back to extracting the HTML attachment from the stored EML so the Inbox popup shows the report without requiring a reset.
|
||||
|
||||
================================================================================================================================================
|
||||
## v0.1.18
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user