feat(gui): câblage upload diagnostics en fin de run (E3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -193,6 +193,7 @@ class AnonymisationApp(ctk.CTk):
|
||||
on_theme_change=self.set_theme,
|
||||
current_theme=self._theme_name,
|
||||
usage_reporter=self._report_usage,
|
||||
diag_reporter=self._report_diagnostics,
|
||||
)
|
||||
if key == "cfg":
|
||||
return ConfigTab(self._content, palette=p, state=self._config, config_path=self._user_config_path)
|
||||
@@ -243,6 +244,36 @@ class AnonymisationApp(ctk.CTk):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _report_diagnostics(self, summary) -> None:
|
||||
"""Envoie les diagnostics en fin de run (non bloquant, best-effort).
|
||||
|
||||
N'envoie rien si aucune licence locale valide. Ne lève jamais.
|
||||
"""
|
||||
try:
|
||||
from gui_v6 import __version__ as gui_version
|
||||
from gui_v6 import diagnostics
|
||||
from gui_v6.logging_setup import log_file_path
|
||||
from gui_v6.machine_id import default_machine_id
|
||||
|
||||
session = self._usage_session()
|
||||
if session is None:
|
||||
return
|
||||
status = self._safe_local_status()
|
||||
base_url = getattr(self._license_client, "_base_url", "") or resolve_portal_url()
|
||||
spool = log_file_path().parent / "diagnostics_spool.jsonl"
|
||||
diagnostics.report_run_diagnostics(
|
||||
summary,
|
||||
base_url=base_url,
|
||||
license_ref=getattr(status, "license_ref", None),
|
||||
machine_id=default_machine_id(),
|
||||
session=session,
|
||||
app_name="gui_v6",
|
||||
app_version=gui_version,
|
||||
spool_path=spool,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _show(self, key: str) -> None:
|
||||
self._active = key
|
||||
self._refresh_tabbar()
|
||||
|
||||
@@ -68,6 +68,7 @@ class UsageTab(ctk.CTkFrame):
|
||||
on_theme_change=None,
|
||||
current_theme: str = theme_mod.DEFAULT_THEME,
|
||||
usage_reporter=None,
|
||||
diag_reporter=None,
|
||||
**kwargs,
|
||||
):
|
||||
self._p = palette or theme_mod.get_palette(current_theme)
|
||||
@@ -80,6 +81,9 @@ class UsageTab(ctk.CTkFrame):
|
||||
# Callback(summary) appelé en fin de run pour la télémétrie d'usage
|
||||
# (envoi non bloquant, injecté par l'app avec le contexte licence).
|
||||
self._usage_reporter = usage_reporter
|
||||
# Callback(summary) appelé en fin de run pour les diagnostics RGPD
|
||||
# (envoi non bloquant, injecté par l'app avec le contexte licence).
|
||||
self._diag_reporter = diag_reporter
|
||||
|
||||
self._input_path: Path | None = None
|
||||
self._output_dir: Path | None = None
|
||||
@@ -320,6 +324,7 @@ class UsageTab(ctk.CTkFrame):
|
||||
self._show_results(summary)
|
||||
self._show_failure_hint(summary)
|
||||
self._send_usage_telemetry(summary)
|
||||
self._send_diagnostics(summary)
|
||||
|
||||
def _send_usage_telemetry(self, summary) -> None:
|
||||
"""Envoie la télémétrie d'usage en fin de run, sans bloquer l'UI ni le run."""
|
||||
@@ -335,6 +340,20 @@ class UsageTab(ctk.CTkFrame):
|
||||
|
||||
threading.Thread(target=work, daemon=True).start()
|
||||
|
||||
def _send_diagnostics(self, summary) -> None:
|
||||
"""Envoie les diagnostics en fin de run, sans bloquer l'UI ni le run."""
|
||||
reporter = self._diag_reporter
|
||||
if reporter is None:
|
||||
return
|
||||
|
||||
def work():
|
||||
try:
|
||||
reporter(summary)
|
||||
except Exception:
|
||||
pass # un échec diagnostic ne doit jamais remonter
|
||||
|
||||
threading.Thread(target=work, daemon=True).start()
|
||||
|
||||
def _show_results(self, summary) -> None:
|
||||
p = self._p
|
||||
for w in self._stats_row.winfo_children():
|
||||
|
||||
@@ -108,3 +108,29 @@ def test_flush_spool_sends_and_clears(tmp_path):
|
||||
sent = diagnostics.flush_spool(spool, diagnostics.DiagnosticsClient(
|
||||
"https://app.aivanov.eu", session=_FakeSession(status_code=200)))
|
||||
assert sent == 2 and not spool.exists()
|
||||
|
||||
|
||||
def test_tab_send_diagnostics_calls_reporter():
|
||||
import threading
|
||||
from gui_v6.tabs.tab_usage import UsageTab
|
||||
|
||||
tab = object.__new__(UsageTab) # pas de Tk : on teste juste le helper
|
||||
seen = {}
|
||||
done = threading.Event()
|
||||
|
||||
def reporter(summary):
|
||||
seen["summary"] = summary
|
||||
done.set()
|
||||
|
||||
tab._diag_reporter = reporter
|
||||
tab._send_diagnostics(SimpleNamespace(documents=[], failed=0))
|
||||
assert done.wait(timeout=2.0)
|
||||
assert seen["summary"] is not None
|
||||
|
||||
|
||||
def test_tab_send_diagnostics_noop_without_reporter():
|
||||
from gui_v6.tabs.tab_usage import UsageTab
|
||||
|
||||
tab = object.__new__(UsageTab)
|
||||
tab._diag_reporter = None
|
||||
tab._send_diagnostics(SimpleNamespace(documents=[])) # ne lève pas
|
||||
|
||||
Reference in New Issue
Block a user