From 3c5872a4b6b64e9f40a506f251edaabe20fda6de Mon Sep 17 00:00:00 2001 From: Hisenberg Date: Tue, 30 Jun 2026 14:40:02 +0200 Subject: [PATCH] fix: add user email to sentry --- plain2code.py | 8 +++++--- plain2code_state.py | 1 + plain2code_telemetry.py | 2 ++ tests/test_telemetry.py | 17 ++++++++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/plain2code.py b/plain2code.py index 03271b6..343332c 100644 --- a/plain2code.py +++ b/plain2code.py @@ -140,8 +140,8 @@ def setup_logging( return logging.getLevelName(configured_log_level) -def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI): - """Check API connectivity and validate API key and client version.""" +def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI) -> Optional[str]: + """Check API connectivity, validate API key and client version, and return the user email.""" response = codeplainAPI.connection_check(system_config.client_version) if not response.get("api_key_valid", False): @@ -158,6 +158,8 @@ def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI): "Please update using: uv tool upgrade codeplain" ) + return response.get("user_email") + def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> None: """Warn when any loaded module (including required modules) defines acceptance tests @@ -201,7 +203,7 @@ def render( # noqa: C901 assert args.api is not None and args.api != "", "API URL is required" codeplainAPI.api_url = args.api - _check_connection(codeplainAPI) + run_state.user_email = _check_connection(codeplainAPI) stop_event = threading.Event() enter_pause_event = threading.Event() diff --git a/plain2code_state.py b/plain2code_state.py index 08268e2..e508cf2 100644 --- a/plain2code_state.py +++ b/plain2code_state.py @@ -27,6 +27,7 @@ def __init__(self, spec_filename: str, replay_with: Optional[str] = None): self.current_module: Optional[str] = None self.current_frid: Optional[str] = None self.current_render_state: Optional[str] = None + self.user_email: Optional[str] = None def increment_call_count(self): self.call_count += 1 diff --git a/plain2code_telemetry.py b/plain2code_telemetry.py index cf3214d..8e1776f 100644 --- a/plain2code_telemetry.py +++ b/plain2code_telemetry.py @@ -98,6 +98,8 @@ def capture_crash(exc_info, run_state: Optional[RunState], args) -> bool: scope.set_tag("render_state", run_state.current_render_state) scope.set_tag("current_module", run_state.current_module) scope.set_tag("current_frid", run_state.current_frid) + if run_state.user_email: + scope.set_user({"email": run_state.user_email}) scope.set_tag("headless", bool(getattr(args, "headless", False))) scope.set_tag("unittests_script_provided", bool(getattr(args, "unittests_script", None))) scope.set_tag("conformance_tests_script_provided", bool(getattr(args, "conformance_tests_script", None))) diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index 3a70498..4ceafb5 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -80,12 +80,14 @@ def test_capture_crash_sends_event_with_tags(transport): run_state.current_module = "my_module" run_state.current_frid = "2.1" run_state.current_render_state = "IMPLEMENTING_FRID" + run_state.user_email = "user@codeplain.ai" assert capture_crash(make_exc_info(KeyError("boom")), run_state, make_args(headless=True)) sentry_sdk.flush(timeout=2) assert len(transport.events) == 1 - tags = transport.events[0]["tags"] + event = transport.events[0] + tags = event["tags"] assert tags["render_id"] == run_state.render_id assert tags["current_module"] == "my_module" assert tags["current_frid"] == "2.1" @@ -94,6 +96,19 @@ def test_capture_crash_sends_event_with_tags(transport): assert tags["unittests_script_provided"] is True assert tags["conformance_tests_script_provided"] is False assert tags["prepare_environment_script_provided"] is False + assert event["user"]["email"] == "user@codeplain.ai" + + +def test_capture_crash_without_user_email(transport): + init_with_transport(transport) + + run_state = RunState(spec_filename="test.plain") + + assert capture_crash(make_exc_info(KeyError("boom")), run_state, make_args()) + sentry_sdk.flush(timeout=2) + + assert len(transport.events) == 1 + assert "email" not in transport.events[0].get("user", {}) def test_capture_crash_without_run_state(transport):