fix(translate): M1-M3 medium + L1-L2 low bugs from QA review
M1: ReDoS guard — 500 char max + try/except on re.compile M2: Performance note — O(n×m) regex documented for future optimization M3: Verified test key names correct (metrics.py transforms cumulative_* → short) L1: Removed @staticmethod no-op from module-level function L2: Added aria-label to emoji indicators in CorrectionCell
This commit is contained in:
@@ -37,7 +37,6 @@ def _run_to_response(run: Any) -> dict:
|
||||
|
||||
# #region _dict_to_response [C:2] [TYPE Function]
|
||||
# @BRIEF Convert a TerminologyDictionary ORM model to a response dict with computed entry_count.
|
||||
@staticmethod
|
||||
def _dict_to_response(d: Any, entry_count: int = 0) -> dict:
|
||||
return {
|
||||
"id": d.id,
|
||||
|
||||
@@ -658,6 +658,10 @@ class DictionaryManager:
|
||||
if not all_entries:
|
||||
return []
|
||||
|
||||
# PERFORMANCE NOTE: O(rows × entries) regex matching per batch.
|
||||
# Acceptable for current scale (50-100 rows × <1000 entries).
|
||||
# For larger batches (>500 rows or >5000 entries), consider
|
||||
# replacing with an Aho-Corasick automaton (e.g., pyahocorasick).
|
||||
matched: list[dict[str, Any]] = []
|
||||
seen_source_match: set = set()
|
||||
|
||||
|
||||
@@ -825,13 +825,19 @@ class InlineCorrectionService:
|
||||
class BulkFindReplaceService:
|
||||
"""Handle bulk find-and-replace on TranslationLanguage entries."""
|
||||
|
||||
MAX_PATTERN_LENGTH = 500
|
||||
|
||||
@staticmethod
|
||||
def _compile_pattern(pattern: str, is_regex: bool) -> re.Pattern:
|
||||
"""Compile a search pattern (regex or plain text)."""
|
||||
import re
|
||||
if is_regex:
|
||||
return re.compile(pattern)
|
||||
return re.compile(re.escape(pattern))
|
||||
if len(pattern) > BulkFindReplaceService.MAX_PATTERN_LENGTH:
|
||||
raise ValueError(
|
||||
f"Pattern too long (max {BulkFindReplaceService.MAX_PATTERN_LENGTH} characters)"
|
||||
)
|
||||
try:
|
||||
return re.compile(pattern) if is_regex else re.compile(re.escape(pattern))
|
||||
except re.error as e:
|
||||
raise ValueError(f"Invalid regex pattern: {e}")
|
||||
|
||||
@staticmethod
|
||||
def _find_matching_entries(
|
||||
|
||||
@@ -608,7 +608,7 @@ class OverrideLanguageRequest(BaseModel):
|
||||
# #region BulkFindReplaceRequest [C:1] [TYPE Class]
|
||||
# @BRIEF Schema for bulk find-and-replace within translations for a target language.
|
||||
class BulkFindReplaceRequest(BaseModel):
|
||||
find_pattern: str = Field(..., description="Text or regex pattern to find")
|
||||
find_pattern: str = Field(..., description="Text or regex pattern to find", max_length=500)
|
||||
is_regex: bool = Field(False, description="Whether find_pattern is a regular expression")
|
||||
replacement_text: str = Field(..., description="Replacement text")
|
||||
target_language: str = Field(..., description="BCP-47 language code to target")
|
||||
@@ -617,6 +617,14 @@ class BulkFindReplaceRequest(BaseModel):
|
||||
dictionary_id: str | None = Field(None, description="Target dictionary ID for submitting corrections")
|
||||
usage_notes: str | None = Field(None, description="Usage notes for dictionary entries")
|
||||
submit_to_dictionary_with_context: bool = Field(False, description="If true, auto-capture source row context when submitting to dictionary")
|
||||
|
||||
@field_validator("find_pattern")
|
||||
@classmethod
|
||||
def validate_pattern_length(cls, v: str) -> str:
|
||||
"""Reject patterns longer than 500 characters to prevent ReDoS."""
|
||||
if len(v) > 500:
|
||||
raise ValueError("Pattern too long (max 500 characters)")
|
||||
return v
|
||||
# #endregion BulkFindReplaceRequest
|
||||
|
||||
|
||||
|
||||
@@ -183,11 +183,12 @@
|
||||
{:else if uxState === 'saved'}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-700">{displayValue}</span>
|
||||
<span class="text-xs text-green-600" title="Saved">📝</span>
|
||||
<span class="text-xs text-green-600" title="Saved" aria-label="Edited" role="img">📝</span>
|
||||
<button
|
||||
onclick={openDictPopup}
|
||||
class="ml-1 px-1.5 py-0.5 text-xs bg-amber-100 text-amber-700 rounded hover:bg-amber-200 transition-colors"
|
||||
title="Submit to Dictionary"
|
||||
aria-label="Submit to Dictionary"
|
||||
>
|
||||
📕 Dictionary
|
||||
</button>
|
||||
@@ -204,7 +205,7 @@
|
||||
{:else if uxState === 'dict_submitted'}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-700">{displayValue}</span>
|
||||
<span class="text-xs text-green-600 font-medium" title="Saved to dictionary">✅ Dictionary ✓</span>
|
||||
<span class="text-xs text-green-600 font-medium" title="Saved to dictionary" aria-label="Submitted to dictionary" role="img">✅ Dictionary ✓</span>
|
||||
</div>
|
||||
|
||||
<!-- Error mode -->
|
||||
|
||||
Reference in New Issue
Block a user