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,
|
on_theme_change=self.set_theme,
|
||||||
current_theme=self._theme_name,
|
current_theme=self._theme_name,
|
||||||
usage_reporter=self._report_usage,
|
usage_reporter=self._report_usage,
|
||||||
|
diag_reporter=self._report_diagnostics,
|
||||||
)
|
)
|
||||||
if key == "cfg":
|
if key == "cfg":
|
||||||
return ConfigTab(self._content, palette=p, state=self._config, config_path=self._user_config_path)
|
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:
|
except Exception:
|
||||||
pass
|
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:
|
def _show(self, key: str) -> None:
|
||||||
self._active = key
|
self._active = key
|
||||||
self._refresh_tabbar()
|
self._refresh_tabbar()
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class UsageTab(ctk.CTkFrame):
|
|||||||
on_theme_change=None,
|
on_theme_change=None,
|
||||||
current_theme: str = theme_mod.DEFAULT_THEME,
|
current_theme: str = theme_mod.DEFAULT_THEME,
|
||||||
usage_reporter=None,
|
usage_reporter=None,
|
||||||
|
diag_reporter=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
self._p = palette or theme_mod.get_palette(current_theme)
|
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
|
# 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).
|
# (envoi non bloquant, injecté par l'app avec le contexte licence).
|
||||||
self._usage_reporter = usage_reporter
|
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._input_path: Path | None = None
|
||||||
self._output_dir: Path | None = None
|
self._output_dir: Path | None = None
|
||||||
@@ -320,6 +324,7 @@ class UsageTab(ctk.CTkFrame):
|
|||||||
self._show_results(summary)
|
self._show_results(summary)
|
||||||
self._show_failure_hint(summary)
|
self._show_failure_hint(summary)
|
||||||
self._send_usage_telemetry(summary)
|
self._send_usage_telemetry(summary)
|
||||||
|
self._send_diagnostics(summary)
|
||||||
|
|
||||||
def _send_usage_telemetry(self, summary) -> None:
|
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."""
|
"""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()
|
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:
|
def _show_results(self, summary) -> None:
|
||||||
p = self._p
|
p = self._p
|
||||||
for w in self._stats_row.winfo_children():
|
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(
|
sent = diagnostics.flush_spool(spool, diagnostics.DiagnosticsClient(
|
||||||
"https://app.aivanov.eu", session=_FakeSession(status_code=200)))
|
"https://app.aivanov.eu", session=_FakeSession(status_code=200)))
|
||||||
assert sent == 2 and not spool.exists()
|
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