Backend:
- deduplicate param in GET /runs/{id}/records — NOT EXISTS subquery
with (created_at, id) tiebreaker for one row per source_hash
- source_data and source_hash added to records JSON response
- fix missing status import in _run_history_routes.py (NameError)
- fix tab/space mixup in orchestrator_aggregator.py
- remove duplicated @RELATION edges in metrics.py
- fix #region/#endregion style and @PURPOSE→@BRIEF in metrics.py
Frontend:
- RunTabContent: summary metrics bar (fetchJobMetrics) with HelpTooltips
- RunTabContent: trigger_type badge, duration, cache rate in run rows
- TranslationRunResult: deduplicate=true by default, paginated Load more
- TranslationRunResult: per-language token_count and estimated_cost
- TranslationRunResult: collapsible batch breakdown with timing
- TranslationRunResult: Bulk Replace button in header and records table
- TranslationRunResult: source_data key values shown under source text
i18n: all new keys in EN + RU (load_more_records, showing_records,
sum_*, help_sum_*, trigger_*, batch_*, duration_label, cost_label,
cache_rate, load_more_records)
149 lines
5.4 KiB
Python
149 lines
5.4 KiB
Python
# #region orchestrator_query [C:3] [TYPE Module] [SEMANTICS translate, query, records, history]
|
|
# @BRIEF Query translation run records and history with pagination.
|
|
# @RELATION DEPENDS_ON -> [TranslationRecord]
|
|
# @RELATION DEPENDS_ON -> [TranslationRun]
|
|
|
|
from typing import Any
|
|
|
|
from sqlalchemy.orm import Session, selectinload
|
|
|
|
from ...models.translate import TranslationRecord, TranslationRun
|
|
|
|
|
|
# #region get_run_records [C:2] [TYPE Function] [SEMANTICS records, query, paginated]
|
|
# @BRIEF Get paginated records for a run. Supports deduplicate=true to return unique source_hash rows.
|
|
def get_run_records(
|
|
db: Session,
|
|
run_id: str,
|
|
page: int = 1,
|
|
page_size: int = 50,
|
|
status_filter: str | None = None,
|
|
deduplicate: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Get paginated records for a run."""
|
|
import sqlalchemy as sa
|
|
|
|
query = (
|
|
db.query(TranslationRecord)
|
|
.options(selectinload(TranslationRecord.languages))
|
|
.filter(TranslationRecord.run_id == run_id)
|
|
)
|
|
if status_filter:
|
|
query = query.filter(TranslationRecord.status == status_filter)
|
|
|
|
if deduplicate:
|
|
# One row per unique source_hash using NOT EXISTS on (source_hash, created_at, id).
|
|
# When created_at ties, the larger id wins. No relationship joins inside the
|
|
# correlated subquery — avoids row multiplication from languages.
|
|
RecAlias = sa.orm.aliased(TranslationRecord)
|
|
newer_exists = (
|
|
db.query(RecAlias.id)
|
|
.filter(
|
|
RecAlias.run_id == run_id,
|
|
RecAlias.source_hash == TranslationRecord.source_hash,
|
|
RecAlias.source_hash.isnot(None),
|
|
RecAlias.source_hash != "",
|
|
sa.or_(
|
|
RecAlias.created_at > TranslationRecord.created_at,
|
|
sa.and_(
|
|
RecAlias.created_at == TranslationRecord.created_at,
|
|
RecAlias.id > TranslationRecord.id,
|
|
),
|
|
),
|
|
)
|
|
).exists()
|
|
|
|
query = query.filter(
|
|
TranslationRecord.source_hash.isnot(None),
|
|
TranslationRecord.source_hash != "",
|
|
~newer_exists,
|
|
)
|
|
|
|
total = query.count()
|
|
offset_val = (page - 1) * page_size
|
|
records = (
|
|
query.order_by(TranslationRecord.created_at.desc())
|
|
.offset(offset_val)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": r.id,
|
|
"batch_id": r.batch_id,
|
|
"source_sql": r.source_sql,
|
|
"target_sql": r.target_sql,
|
|
"source_object_type": r.source_object_type,
|
|
"source_object_id": r.source_object_id,
|
|
"source_object_name": r.source_object_name,
|
|
"source_data": r.source_data,
|
|
"source_hash": r.source_hash,
|
|
"status": r.status,
|
|
"error_message": r.error_message,
|
|
"created_at": r.created_at.isoformat() if r.created_at else None,
|
|
"languages": [
|
|
{
|
|
"language_code": tl.language_code,
|
|
"translated_value": tl.translated_value,
|
|
"final_value": tl.final_value or tl.translated_value,
|
|
"source_language_detected": tl.source_language_detected,
|
|
"status": tl.status,
|
|
"needs_review": tl.needs_review or False,
|
|
}
|
|
for tl in (r.languages or [])
|
|
],
|
|
}
|
|
for r in records
|
|
],
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"status_filter": status_filter,
|
|
"deduplicate": deduplicate,
|
|
}
|
|
# #endregion get_run_records
|
|
|
|
# #region get_run_history [C:2] [TYPE Function] [SEMANTICS history, query, paginated]
|
|
# @BRIEF Get run history for a job with pagination. Returns trigger_type, cache_hits for enhanced run list display.
|
|
def get_run_history(
|
|
db: Session,
|
|
job_id: str,
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
) -> tuple[int, list[dict[str, Any]]]:
|
|
"""Get run history for a job with pagination."""
|
|
query = db.query(TranslationRun).filter(TranslationRun.job_id == job_id)
|
|
total = query.count()
|
|
runs = (
|
|
query.order_by(TranslationRun.created_at.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
.all()
|
|
)
|
|
|
|
return total, [
|
|
{
|
|
"id": r.id,
|
|
"job_id": r.job_id,
|
|
"status": r.status,
|
|
"trigger_type": r.trigger_type,
|
|
"started_at": r.started_at.isoformat() if r.started_at else None,
|
|
"completed_at": r.completed_at.isoformat() if r.completed_at else None,
|
|
"error_message": r.error_message,
|
|
"total_records": r.total_records or 0,
|
|
"successful_records": r.successful_records or 0,
|
|
"failed_records": r.failed_records or 0,
|
|
"skipped_records": r.skipped_records or 0,
|
|
"cache_hits": r.cache_hits or 0,
|
|
"insert_status": r.insert_status,
|
|
"superset_execution_id": r.superset_execution_id,
|
|
"created_by": r.created_by,
|
|
"created_at": r.created_at.isoformat() if r.created_at else None,
|
|
}
|
|
for r in runs
|
|
]
|
|
# #endregion get_run_history
|
|
# #endregion orchestrator_query
|