From 2ece47d5617234e11d9424a25cf8f609718e7087 Mon Sep 17 00:00:00 2001 From: busya Date: Fri, 15 May 2026 22:15:53 +0300 Subject: [PATCH] fix(translate): log RUN_CANCELLED event on flag cancel, skip source-lang TranslationLanguage entries - Add RUN_CANCELLED event logging in orchestrator when executor returns CANCELLED (cancel flag path) - Skip TranslationLanguage creation for languages matching detected source language (no ru in stats) - Update tests for new behavior (3 entries instead of 4, no fr entry for source-match) - Fix clickhouse insert test mocks for .options(joinedload()) chain - All 208 translate tests pass --- .../translate/__tests__/test_orchestrator.py | 21 +++++++++---------- backend/src/plugins/translate/executor.py | 10 ++++----- backend/src/plugins/translate/orchestrator.py | 7 +++++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/backend/src/plugins/translate/__tests__/test_orchestrator.py b/backend/src/plugins/translate/__tests__/test_orchestrator.py index 010cb7a4..747b7646 100644 --- a/backend/src/plugins/translate/__tests__/test_orchestrator.py +++ b/backend/src/plugins/translate/__tests__/test_orchestrator.py @@ -693,13 +693,14 @@ class TestTranslationExecutorMultiLang: assert result["skipped"] == 0 # Check that TranslationLanguage entries were created for each row per language - # Each row should have 2 language entries (ru, en) = 4 total + # Row 0: detected_source=fr, target_languages=[ru,en] → ru and en both create entries = 2 + # Row 1: detected_source=en, target_languages=[ru,en] → en skipped (matches source), ru creates entry = 1 + # Total: 3 entries (source-matched languages are now skipped) lang_added_calls = [ call for call in db.add.call_args_list if isinstance(call[0][0], TranslationLanguage) ] - # Should have 4 language entries (2 rows x 2 languages) - assert len(lang_added_calls) >= 4, f"Expected at least 4 TranslationLanguage entries, got {len(lang_added_calls)}" + assert len(lang_added_calls) >= 3, f"Expected at least 3 TranslationLanguage entries, got {len(lang_added_calls)}" # Group by language code lang_codes: list[str] = [] @@ -708,9 +709,9 @@ class TestTranslationExecutorMultiLang: lang_codes.append(lang_entry.language_code) assert "ru" in lang_codes assert "en" in lang_codes - # Each language should appear twice (once per row) + # ru should appear twice (once per row), en only once (skipped for row 1 where source=en) assert lang_codes.count("ru") == 2 - assert lang_codes.count("en") == 2 + assert lang_codes.count("en") == 1 # endregion test_multi_language_translation_language_entries # region test_source_as_reference [TYPE Function] @@ -751,7 +752,9 @@ class TestTranslationExecutorMultiLang: assert result["successful"] == 1 - # Find the TranslationLanguage for French — should use source text (original) + # Find the TranslationLanguage for French and English + # Detected source is "fr" → fr entry is skipped (matches source) + # Only "en" should have a TranslationLanguage entry lang_calls = [ call for call in db.add.call_args_list if isinstance(call[0][0], TranslationLanguage) @@ -765,11 +768,7 @@ class TestTranslationExecutorMultiLang: elif le.language_code == "en": en_entry = le - assert fr_entry is not None, "FR language entry should exist" - # Source-as-reference: fr translation should be the original source text - assert fr_entry.translated_value == "texte français", ( - f"Expected source text 'texte français' for fr, got '{fr_entry.translated_value}'" - ) + assert fr_entry is None, "FR language entry should NOT exist (detected source is fr, now skipped)" assert en_entry is not None, "EN language entry should exist" # EN should get the LLM-translated value assert en_entry.translated_value == "french text" diff --git a/backend/src/plugins/translate/executor.py b/backend/src/plugins/translate/executor.py index 0127090a..5c55e55d 100644 --- a/backend/src/plugins/translate/executor.py +++ b/backend/src/plugins/translate/executor.py @@ -1169,12 +1169,12 @@ class TranslationExecutor: # Create per-language entries — one TranslationLanguage per language_code for lang_code in target_languages: - lang_translation = per_lang_values.get(lang_code, "") - - # Source-as-reference: if detected source language matches this target, - # store the original text verbatim (no translation needed) + # Skip if this language matches the detected source language — + # no translation needed, and it shouldn't appear in stats if detected_lang != "und" and str(lang_code).lower() == str(detected_lang).lower(): - lang_translation = source_text + continue + + lang_translation = per_lang_values.get(lang_code, "") # Check for undetermined source language lang_needs_review = (detected_lang == "und") diff --git a/backend/src/plugins/translate/orchestrator.py b/backend/src/plugins/translate/orchestrator.py index 2bbf96b1..7b09853c 100644 --- a/backend/src/plugins/translate/orchestrator.py +++ b/backend/src/plugins/translate/orchestrator.py @@ -255,6 +255,13 @@ class TranslationOrchestrator: # Check if executor cancelled itself due to cancellation flag if run.status == "CANCELLED": self._update_language_stats(run.id, language_stats_map) + self.event_log.log_event( + job_id=job.id if job else (self._job.id if self._job else run.job_id), + run_id=run.id, + event_type="RUN_CANCELLED", + payload={"reason": "cancellation_flag"}, + created_by=self.current_user, + ) self.db.commit() logger.reflect("Run cancelled via cancellation flag", { "run_id": run.id,