Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions backend/app/documents/query.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import Iterable

from sqlalchemy import case, func, select, Row
from sqlalchemy import Row, case, func, select
from sqlalchemy.orm import Session

from app.base.exceptions import BaseQueryException
Expand Down Expand Up @@ -96,7 +96,7 @@ def get_document_records_paged(
repetitions_subquery = (
select(
DocumentRecord.source,
func.count(DocumentRecord.id).label('repetitions_count')
func.count(DocumentRecord.id).label("repetitions_count"),
)
.filter(DocumentRecord.document_id == doc.id)
.group_by(DocumentRecord.source)
Expand All @@ -106,12 +106,14 @@ def get_document_records_paged(
return self.__db.execute(
select(
DocumentRecord,
func.coalesce(repetitions_subquery.c.repetitions_count, 0).label('repetitions_count')
func.coalesce(repetitions_subquery.c.repetitions_count, 0).label(
"repetitions_count"
),
)
.filter(DocumentRecord.document_id == doc.id)
.outerjoin(
repetitions_subquery,
DocumentRecord.source == repetitions_subquery.c.source
DocumentRecord.source == repetitions_subquery.c.source,
)
.order_by(DocumentRecord.id)
.offset(page_records * page)
Expand All @@ -131,19 +133,39 @@ def update_record(
raise NotFoundDocumentRecordExc()

record.target = data.target

if data.approved is not None:
record.approved = data.approved

if data.approved is True:
bound_tm = None
for memory in record.document.memory_associations:
if memory.mode == TmMode.write:
bound_tm = memory.tm_id

if bound_tm:
TranslationMemoryQuery(self.__db).add_or_update_record(
bound_tm, record.source, record.target
# If update_repetitions is True, find all records with the same source
if data.update_repetitions:
repeated_records = (
self.__db.execute(
select(DocumentRecord).filter(
DocumentRecord.document_id == record.document_id,
DocumentRecord.source == record.source,
)
)
.scalars()
.all()
)

# Update all repeated records
for repeated_record in repeated_records:
repeated_record.target = data.target
if data.approved is not None:
repeated_record.approved = data.approved

if data.approved is True:
bound_tm = None
for memory in record.document.memory_associations:
if memory.mode == TmMode.write:
bound_tm = memory.tm_id

if bound_tm:
TranslationMemoryQuery(self.__db).add_or_update_record(
bound_tm, record.source, record.target
)

self.__db.commit()
return record
Expand Down
1 change: 1 addition & 0 deletions backend/app/documents/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class DocumentRecordUpdateResponse(Identified):
class DocumentRecordUpdate(BaseModel):
target: str
approved: Optional[bool]
update_repetitions: bool


class DocumentProcessingSettings(BaseModel):
Expand Down
9 changes: 7 additions & 2 deletions backend/app/routers/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,13 @@ def update_doc_record(
db: Annotated[Session, Depends(get_db)],
) -> doc_schema.DocumentRecordUpdateResponse:
try:
record = GenericDocsQuery(db).update_record(record_id, record)
return record
updated_record = GenericDocsQuery(db).update_record(record_id, record)
return doc_schema.DocumentRecordUpdateResponse(
id=updated_record.id,
source=updated_record.source,
target=updated_record.target,
approved=updated_record.approved,
)
except NotFoundDocumentRecordExc as e:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Record not found"
Expand Down
112 changes: 107 additions & 5 deletions backend/tests/routers/test_routes_doc_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ def test_doc_records_returns_404_for_nonexistent_document(
@pytest.mark.parametrize(
"arguments",
[
{"target": "Updated", "approved": None},
{"target": "Updated", "approved": True},
{"target": "Updated", "approved": None, "update_repetitions": False},
{"target": "Updated", "approved": True, "update_repetitions": False},
],
)
def test_can_update_doc_record(
Expand Down Expand Up @@ -201,7 +201,12 @@ def test_record_approving_creates_memory(
s.commit()

response = user_logged_client.put(
"/document/record/1", json={"target": "Updated", "approved": True}
"/document/record/1",
json={
"target": "Updated",
"approved": True,
"update_repetitions": False,
},
)
assert response.status_code == 200, response.text

Expand Down Expand Up @@ -251,7 +256,12 @@ def test_record_approving_updates_memory(
s.commit()

response = user_logged_client.put(
"/document/record/1", json={"target": "Updated", "approved": True}
"/document/record/1",
json={
"target": "Updated",
"approved": True,
"update_repetitions": False,
},
)
assert response.status_code == 200, response.text

Expand All @@ -274,7 +284,12 @@ def test_returns_404_for_nonexistent_doc_when_updating_record(
user_logged_client: TestClient,
):
response = user_logged_client.put(
"/document/record/3", json={"target": "Updated", "approved": None}
"/document/record/3",
json={
"target": "Updated",
"approved": None,
"update_repetitions": False,
},
)
assert response.status_code == 404

Expand All @@ -298,3 +313,90 @@ def test_returns_404_for_nonexistent_record(
"/document/1/record/3", json={"target": "Updated"}
)
assert response.status_code == 404


def test_can_update_doc_record_with_repetitions(
user_logged_client: TestClient, session: Session
):
"""Test updating all records with the same source text"""
with session as s:
records = [
DocumentRecord(source="Hello World", target="Привет Мир"),
DocumentRecord(source="Hello World", target="Здравствуйте Мир"),
DocumentRecord(source="Goodbye", target="Пока"),
]
s.add(
Document(
name="test_doc.txt",
type=DocumentType.txt,
records=records,
processing_status="pending",
created_by=1,
)
)
s.commit()

# Update record 1 with repetition update enabled
response = user_logged_client.put(
"/document/record/1",
json={"target": "Updated Hello", "approved": True, "update_repetitions": True},
)
assert response.status_code == 200

with session as s:
# Check that all records with "Hello World" source were updated
record1 = s.query(DocumentRecord).filter(DocumentRecord.id == 1).one()
record2 = s.query(DocumentRecord).filter(DocumentRecord.id == 2).one()
record3 = s.query(DocumentRecord).filter(DocumentRecord.id == 3).one()

assert record1.target == "Updated Hello"
assert record1.approved is True

assert record2.target == "Updated Hello" # updated
assert record2.approved is True

assert record3.target == "Пока" # unchanged (different source)
assert record3.approved is False


def test_update_repetitions_default_behavior(
user_logged_client: TestClient, session: Session
):
"""Test that update_repetitions defaults to False when not specified"""
with session as s:
records = [
DocumentRecord(source="Hello World", target="Привет Мир"),
DocumentRecord(source="Hello World", target="Здравствуйте Мир"),
]
s.add(
Document(
name="test_doc.txt",
type=DocumentType.txt,
records=records,
processing_status="pending",
created_by=1,
)
)
s.commit()

# Update without specifying update_repetitions (should default to False)
response = user_logged_client.put(
"/document/record/1",
json={
"target": "Updated Hello",
"approved": True,
"update_repetitions": False,
},
)
assert response.status_code == 200

with session as s:
# Check that only record 1 was updated
record1 = s.query(DocumentRecord).filter(DocumentRecord.id == 1).one()
record2 = s.query(DocumentRecord).filter(DocumentRecord.id == 2).one()

assert record1.target == "Updated Hello"
assert record1.approved is True

assert record2.target == "Здравствуйте Мир" # unchanged
assert record2.approved is False
8 changes: 6 additions & 2 deletions backend/tests/routers/test_routes_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,9 @@ def test_setting_glossaries_returns_404_for_non_existing_glossaries(
assert response.status_code == 404


def test_get_doc_records_with_repetitions(user_logged_client: TestClient, session: Session):
def test_get_doc_records_with_repetitions(
user_logged_client: TestClient, session: Session
):
"""Test that document records endpoint returns repetition counts"""
with session as s:
records = [
Expand Down Expand Up @@ -947,7 +949,9 @@ def test_get_doc_records_with_repetitions(user_logged_client: TestClient, sessio

# Check that repetition counts are correct
# "Hello World" appears 3 times, others appear once
record_counts = {record["source"]: record["repetitions_count"] for record in response_json}
record_counts = {
record["source"]: record["repetitions_count"] for record in response_json
}
assert record_counts["Hello World"] == 3
assert record_counts["Goodbye"] == 1
assert record_counts["Test"] == 1
6 changes: 3 additions & 3 deletions backend/tests/test_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def test_process_task_uses_correct_tm_ids(session: Session):
create_doc(name="small.xliff", type_=DocumentType.xliff),
create_xliff_doc(file_data),
create_task(),
DocMemoryAssociation(doc_id=1, tm_id=2, mode='read')
DocMemoryAssociation(doc_id=1, tm_id=2, mode="read"),
]
)
s.commit()
Expand Down Expand Up @@ -318,8 +318,8 @@ def test_process_task_uses_tm_mode(mode: str, trans_result: str, session: Sessio
create_doc(name="small.xliff", type_=DocumentType.xliff),
create_xliff_doc(file_data),
create_task(usage=TranslationMemoryUsage(mode)),
DocMemoryAssociation(doc_id=1, tm_id=1, mode='read'),
DocMemoryAssociation(doc_id=1, tm_id=2, mode='read')
DocMemoryAssociation(doc_id=1, tm_id=1, mode="read"),
DocMemoryAssociation(doc_id=1, tm_id=2, mode="read"),
]
)
s.commit()
Expand Down
4 changes: 3 additions & 1 deletion backend/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ def process_document(
)

start_time = time.time()
translate_indices = substitute_segments(settings, session, segments, tm_ids, glossary_ids)
translate_indices = substitute_segments(
settings, session, segments, tm_ids, glossary_ids
)
logging.info(
"Segments substitution time: %.2f seconds, speed: %.2f segment/second, segments: %d/%d",
time.time() - start_time,
Expand Down
58 changes: 31 additions & 27 deletions frontend/mocks/documentMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,38 @@ import {
} from '../src/client/services/DocumentService'
import {DocumentStatus} from '../src/client/schemas/DocumentStatus'

const segments = [
{
id: 10000,
approved: false,
source: 'Adventure Hooks',
target: 'Зацепки приключения',
repetitions_count: 2,
},
{
id: 10001,
approved: true,
source: 'Adventure Hooks',
target: 'Зацепки приключения',
repetitions_count: 2,
},
{
id: 10002,
approved: false,
source:
'The moment the Cynidiceans pried the horn from the monolith, their city was doomed.',
target:
'В тот момент, когда кинидийцы извлекли рог из монолита, их город был обречен.',
repetitions_count: 1,
},
]

const docs = [
{
id: 1,
created_by: 12,
records_count: 3,
approved_records_count: 1,
records_count: segments.length,
approved_records_count: segments.filter(({approved}) => approved).length,
name: 'Some document',
status: 'done' as DocumentStatus,
type: 'XLIFF',
Expand All @@ -36,31 +62,9 @@ export const documentMocks = [
({params}) => {
const doc = docs.find((doc) => doc.id === Number(params.id))
if (doc !== undefined) {
return HttpResponse.json<AwaitedReturnType<typeof getDocRecords>>([
{
id: 1,
approved: false,
source: 'Adventure Hooks',
target: 'Зацепки приключения',
repetitions_count: 2,
},
{
id: 2,
approved: true,
source: 'Adventure Hooks',
target: 'Зацепки приключения',
repetitions_count: 2,
},
{
id: 3,
approved: false,
source:
'The moment the Cynidiceans pried the horn from the monolith, their city was doomed.',
target:
'В тот момент, когда кинидийцы извлекли рог из монолита, их город был обречен.',
repetitions_count: 1,
},
])
return HttpResponse.json<AwaitedReturnType<typeof getDocRecords>>(
segments
)
} else {
new HttpResponse(null, {status: 404})
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/client/schemas/DocumentRecordUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
export interface DocumentRecordUpdate {
target: string
approved: boolean | null
update_repetitions: boolean
}
8 changes: 8 additions & 0 deletions frontend/src/client/schemas/DocumentRecordUpdateResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is autogenerated, do not edit directly.

export interface DocumentRecordUpdateResponse {
id: number
source: string
target: string
approved: boolean
}
Loading