Skip to content

Commit 850b282

Browse files
committed
Only recalculate stats of IntegrityError
1 parent c20918d commit 850b282

5 files changed

Lines changed: 109 additions & 3 deletions

File tree

pontoon/base/models/translated_resource.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

33
from django.db import models
4-
from django.db.models import Count, Q, Sum
4+
from django.db.models import Count, F, Q, Sum
55

66
from .locale import Locale
77
from .project import Project
@@ -114,6 +114,39 @@ def stats_data(self) -> dict[str, int]:
114114
"unreviewed": self.unreviewed_strings,
115115
}
116116

117+
def adjust_stats(
118+
self, before: dict[str, int], after: dict[str, int], tr_created: bool
119+
):
120+
if tr_created:
121+
self.total_strings = self.resource.total_strings
122+
self.approved_strings = (
123+
F("approved_strings") + after["approved"] - before["approved"]
124+
)
125+
self.pretranslated_strings = (
126+
F("pretranslated_strings")
127+
+ after["pretranslated"]
128+
- before["pretranslated"]
129+
)
130+
self.strings_with_errors = (
131+
F("strings_with_errors") + after["errors"] - before["errors"]
132+
)
133+
self.strings_with_warnings = (
134+
F("strings_with_warnings") + after["warnings"] - before["warnings"]
135+
)
136+
self.unreviewed_strings = (
137+
F("unreviewed_strings") + after["unreviewed"] - before["unreviewed"]
138+
)
139+
self.save(
140+
update_fields=[
141+
"total_strings",
142+
"approved_strings",
143+
"pretranslated_strings",
144+
"strings_with_errors",
145+
"strings_with_warnings",
146+
"unreviewed_strings",
147+
]
148+
)
149+
117150
def calculate_stats(self, save=True):
118151
"""Update stats, including denormalized ones."""
119152

pontoon/base/models/translation.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dirtyfields import DirtyFieldsMixin
22

33
from django.contrib.postgres.fields import ArrayField
4-
from django.db import models
4+
from django.db import IntegrityError, models, transaction
55
from django.db.models import Count, Q
66
from django.utils import timezone
77

@@ -249,6 +249,8 @@ def save(self, failed_checks=None, *args, **kwargs):
249249
from pontoon.base.models.translated_resource import TranslatedResource
250250
from pontoon.base.models.translation_memory import TranslationMemoryEntry
251251

252+
stats_before = self._get_entity_stats()
253+
252254
super().save(*args, **kwargs)
253255

254256
project = self.entity.resource.project
@@ -320,7 +322,65 @@ def save(self, failed_checks=None, *args, **kwargs):
320322
save_failed_checks(self, failed_checks)
321323

322324
# Update stats AFTER changing approval status.
323-
translatedresource.calculate_stats()
325+
# Use entity-scoped aggregate delta for performance. Fall back to a
326+
# full resource recount on IntegrityError.
327+
stats_after = self._get_entity_stats()
328+
try:
329+
with transaction.atomic():
330+
translatedresource.adjust_stats(stats_before, stats_after, created)
331+
except IntegrityError:
332+
translatedresource.calculate_stats()
333+
334+
def _get_entity_stats(self) -> dict[str, int]:
335+
"""
336+
Aggregate translation stats for this entity+locale pair.
337+
Used to compute before/after deltas in save() without scanning
338+
the entire resource.
339+
"""
340+
stats = Translation.objects.filter(
341+
entity=self.entity,
342+
locale=self.locale,
343+
).aggregate(
344+
approved_count=Count(
345+
"pk",
346+
filter=Q(approved=True, errors__isnull=True, warnings__isnull=True),
347+
),
348+
pretranslated_count=Count(
349+
"pk",
350+
filter=Q(
351+
pretranslated=True, errors__isnull=True, warnings__isnull=True
352+
),
353+
),
354+
errors_count=Count(
355+
"pk",
356+
distinct=True,
357+
filter=Q(
358+
Q(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
359+
& Q(errors__isnull=False)
360+
),
361+
),
362+
warnings_count=Count(
363+
"pk",
364+
distinct=True,
365+
filter=Q(
366+
Q(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
367+
& Q(warnings__isnull=False)
368+
),
369+
),
370+
unreviewed_count=Count(
371+
"pk",
372+
filter=Q(
373+
approved=False, rejected=False, pretranslated=False, fuzzy=False
374+
),
375+
),
376+
)
377+
return {
378+
"approved": stats["approved_count"],
379+
"pretranslated": stats["pretranslated_count"],
380+
"errors": stats["errors_count"],
381+
"warnings": stats["warnings_count"],
382+
"unreviewed": stats["unreviewed_count"],
383+
}
324384

325385
def update_latest_translation(self):
326386
"""

translate/public/locale/en-US/translate.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ notification--translation-unrejected = Translation unrejected
570570
notification--translation-deleted = Translation deleted
571571
notification--translation-saved = Translation saved
572572
notification--unable-to-approve-translation = Unable to approve translation
573+
notification--unable-to-save-translation = Unable to save translation
573574
notification--unable-to-unapprove-translation = Unable to unapprove translation
574575
notification--unable-to-reject-translation = Unable to reject translation
575576
notification--unable-to-unreject-translation = Unable to unreject translation

translate/src/modules/editor/hooks/useSendTranslation.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { usePushNextTranslatable } from '~/modules/entities/hooks';
1818
import {
1919
SAME_TRANSLATION,
2020
TRANSLATION_SAVED,
21+
UNABLE_TO_SAVE_TRANSLATION,
2122
} from '~/modules/notification/messages';
2223
import { updateResource } from '~/modules/resource/actions';
2324
import { updateStats } from '~/modules/stats/actions';
@@ -109,6 +110,8 @@ export function useSendTranslation(): (ignoreWarnings?: boolean) => void {
109110
// The translation that was provided is the same as an existing
110111
// translation for that entity.
111112
showNotification(SAME_TRANSLATION);
113+
} else {
114+
showNotification(UNABLE_TO_SAVE_TRANSLATION);
112115
}
113116

114117
setEditorBusy(false);

translate/src/modules/notification/messages.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ export const TRANSLATION_SAVED: NotificationMessage = {
5757
type: 'info',
5858
};
5959

60+
export const UNABLE_TO_SAVE_TRANSLATION: NotificationMessage = {
61+
content: (
62+
<Localized id='notification--unable-to-save-translation'>
63+
Unable to save translation
64+
</Localized>
65+
),
66+
type: 'error',
67+
};
68+
6069
export const UNABLE_TO_APPROVE_TRANSLATION: NotificationMessage = {
6170
content: (
6271
<Localized id='notification--unable-to-approve-translation'>

0 commit comments

Comments
 (0)