Merge lp:~stevenk/launchpad/remove-suggested-translation-files into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 16421
Proposed branch: lp:~stevenk/launchpad/remove-suggested-translation-files
Merge into: lp:launchpad
Diff against target: 338 lines (+39/-145)
5 files modified
lib/lp/translations/browser/person.py (+3/-19)
lib/lp/translations/browser/tests/test_persontranslationview.py (+15/-47)
lib/lp/translations/interfaces/translationsperson.py (+1/-10)
lib/lp/translations/model/translationsperson.py (+18/-67)
lib/lp/translations/tests/test_potmsgset.py (+2/-2)
To merge this branch: bzr merge lp:~stevenk/launchpad/remove-suggested-translation-files
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+143058@code.launchpad.net

Commit message

Stop displaying random results in the list of suggestions on Person:+translations.

Description of the change

Destroy ITranslationsPerson.suggestTranslatableFiles() and all of its callsites. This should hopefully stop the timeouts on Person:+translations since we no longer want to do an anti-join involving a few million rows. This changes the list that is populated to no longer be made up of roughly '3 * most active projects, 3 * least active projects, 4 * random suggestions', but now '5 * most active projects, 5 * least active projects'.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/translations/browser/person.py'
--- lib/lp/translations/browser/person.py 2012-10-30 15:54:00 +0000
+++ lib/lp/translations/browser/person.py 2013-01-14 03:38:22 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Person-related translations view classes."""4"""Person-related translations view classes."""
@@ -333,17 +333,6 @@
333333
334 return TranslateLinksAggregator().aggregate(pofiles)334 return TranslateLinksAggregator().aggregate(pofiles)
335335
336 def _suggestTargetsForTranslation(self, max_fetch=None):
337 """Suggest translations this person could be helping complete."""
338 person = ITranslationsPerson(self.context)
339 pofiles = person.suggestTranslatableFiles(
340 no_older_than=self.history_horizon)
341
342 if max_fetch is not None:
343 pofiles = pofiles[:max_fetch]
344
345 return TranslateLinksAggregator().aggregate(pofiles)
346
347 @cachedproperty336 @cachedproperty
348 def all_projects_and_packages_to_review(self):337 def all_projects_and_packages_to_review(self):
349 """Top projects and packages for this person to review."""338 """Top projects and packages for this person to review."""
@@ -408,10 +397,10 @@
408 """Suggest translations for this person to help complete."""397 """Suggest translations for this person to help complete."""
409 # Maximum number of translations to list that need the most work398 # Maximum number of translations to list that need the most work
410 # done.399 # done.
411 max_urgent_targets = 3400 max_urgent_targets = 5
412 # Maximum number of translations to list that are almost401 # Maximum number of translations to list that are almost
413 # complete.402 # complete.
414 max_almost_complete_targets = 3403 max_almost_complete_targets = 5
415 # Length of overall list to display.404 # Length of overall list to display.
416 list_length = 10405 list_length = 10
417406
@@ -426,11 +415,6 @@
426 overall, almost_complete, max_almost_complete_targets,415 overall, almost_complete, max_almost_complete_targets,
427 list_length)416 list_length)
428417
429 fetch = 5 * (list_length - len(overall))
430 suggestions = self._suggestTargetsForTranslation(fetch)
431 overall = self._addToTargetsList(
432 overall, suggestions, list_length, list_length)
433
434 return overall418 return overall
435419
436 to_complete_template = ViewPageTemplateFile(420 to_complete_template = ViewPageTemplateFile(
437421
=== modified file 'lib/lp/translations/browser/tests/test_persontranslationview.py'
--- lib/lp/translations/browser/tests/test_persontranslationview.py 2012-10-30 15:54:00 +0000
+++ lib/lp/translations/browser/tests/test_persontranslationview.py 2013-01-14 03:38:22 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -308,38 +308,9 @@
308 nonurgent_pofile.potemplate.productseries.product,308 nonurgent_pofile.potemplate.productseries.product,
309 descriptions[0]['target'])309 descriptions[0]['target'])
310310
311 def test_suggestTargetsForTranslation(self):
312 # suggestTargetsForTranslation finds targets that the person
313 # could help translate.
314 previous_contrib = self._makePOFiles(1, previously_worked_on=True)
315 pofile = self._makePOFiles(1, previously_worked_on=False)[0]
316 self._addUntranslatedMessages(pofile, 1)
317
318 descriptions = self.view._suggestTargetsForTranslation()
319
320 self.assertEqual(1, len(descriptions))
321 self.assertEqual(
322 pofile.potemplate.productseries.product,
323 descriptions[0]['target'])
324
325 def test_suggestTargetsForTranslation_limits_query(self):
326 # The max_fetch argument limits how many POFiles
327 # suggestTargetsForTranslation fetches.
328 previous_contrib = self._makePOFiles(1, previously_worked_on=True)
329 pofiles = self._makePOFiles(3, previously_worked_on=False)
330 for pofile in pofiles:
331 self._addUntranslatedMessages(pofile, 1)
332
333 descriptions = self.view._suggestTargetsForTranslation(max_fetch=2)
334
335 self.assertEqual(2, len(descriptions))
336 self.assertNotEqual(
337 descriptions[0]['target'], descriptions[1]['target'])
338
339 def test_top_projects_and_packages_to_translate(self):311 def test_top_projects_and_packages_to_translate(self):
340 # top_projects_and_packages_to_translate lists targets that the312 # top_projects_and_packages_to_translate lists targets that the
341 # user has worked on and could help translate, followed by313 # user has worked on and could help translate.
342 # randomly suggested ones that also need translation.
343 worked_on = self._makePOFiles(1, previously_worked_on=True)[0]314 worked_on = self._makePOFiles(1, previously_worked_on=True)[0]
344 self._addUntranslatedMessages(worked_on, 1)315 self._addUntranslatedMessages(worked_on, 1)
345 not_worked_on = self._makePOFiles(1, previously_worked_on=False)[0]316 not_worked_on = self._makePOFiles(1, previously_worked_on=False)[0]
@@ -347,16 +318,13 @@
347318
348 descriptions = self.view.top_projects_and_packages_to_translate319 descriptions = self.view.top_projects_and_packages_to_translate
349320
350 self.assertEqual(2, len(descriptions))321 self.assertEqual(1, len(descriptions))
351 self.assertEqual(322 self.assertEqual(
352 worked_on.potemplate.productseries.product,323 worked_on.potemplate.productseries.product,
353 descriptions[0]['target'])324 descriptions[0]['target'])
354 self.assertEqual(
355 not_worked_on.potemplate.productseries.product,
356 descriptions[1]['target'])
357325
358 def test_top_p_n_p_to_translate_caps_existing_involvement(self):326 def test_top_p_n_p_to_translate_caps_existing_involvement(self):
359 # top_projects_and_packages_to_translate shows no more than 6327 # top_projects_and_packages_to_translate shows up to ten
360 # targets that the user has already worked on.328 # targets that the user has already worked on.
361 pofiles = self._makePOFiles(7, previously_worked_on=True)329 pofiles = self._makePOFiles(7, previously_worked_on=True)
362 for pofile in pofiles:330 for pofile in pofiles:
@@ -364,12 +332,12 @@
364332
365 descriptions = self.view.top_projects_and_packages_to_translate333 descriptions = self.view.top_projects_and_packages_to_translate
366334
367 self.assertEqual(6, len(descriptions))335 self.assertEqual(7, len(descriptions))
368336
369 def test_top_p_n_p_to_translate_lists_most_and_least_translated(self):337 def test_top_p_n_p_to_translate_lists_most_and_least_translated(self):
370 # Of the maximum of 6 translations that the user has already338 # Of the maximum of ten translations that the user has already
371 # worked on, the first 3 will be the ones with the most339 # worked on, the first 5 will be the ones with the most
372 # untranslated strings; the last 3 will have the fewest.340 # untranslated strings; the last 5 will have the fewest.
373 # We create a lot more POFiles because internally the property341 # We create a lot more POFiles because internally the property
374 # will fetch many POFiles.342 # will fetch many POFiles.
375 pofiles = self._makePOFiles(50, previously_worked_on=True)343 pofiles = self._makePOFiles(50, previously_worked_on=True)
@@ -380,15 +348,15 @@
380348
381 descriptions = self.view.top_projects_and_packages_to_translate349 descriptions = self.view.top_projects_and_packages_to_translate
382350
383 self.assertEqual(6, len(descriptions))351 self.assertEqual(10, len(descriptions))
384 targets = [item['target'] for item in descriptions]352 targets = [item['target'] for item in descriptions]
385353
386 # We happen to know that no more than 15 POFiles are fetched for354 # We happen to know that no more than 25 POFiles are fetched for
387 # each of the two categories, so the top 3 targets must be taken355 # each of the two categories, so the top 5 targets must be taken
388 # from the last 15 pofiles and the next 3 must be taken from the356 # from the last 25 pofiles and the next 5 must be taken from the
389 # first 15 pofiles.357 # first 25 pofiles.
390 self.assertTrue(set(targets[:3]).issubset(products[15:]))358 self.assertTrue(set(targets[:5]).issubset(products[25:]))
391 self.assertTrue(set(targets[3:]).issubset(products[:15]))359 self.assertTrue(set(targets[5:]).issubset(products[:25]))
392360
393 # No target is mentioned more than once in the listing.361 # No target is mentioned more than once in the listing.
394 self.assertEqual(len(targets), len(set(targets)))362 self.assertEqual(len(targets), len(set(targets)))
395363
=== modified file 'lib/lp/translations/interfaces/translationsperson.py'
--- lib/lp/translations/interfaces/translationsperson.py 2012-10-29 06:13:44 +0000
+++ lib/lp/translations/interfaces/translationsperson.py 2013-01-14 03:38:22 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -72,12 +72,3 @@
72 :return: A query result of `POFile`s ordered by number of72 :return: A query result of `POFile`s ordered by number of
73 untranslated messages.73 untranslated messages.
74 """74 """
75
76 def suggestTranslatableFiles(no_older_than=None):
77 """Suggest `POFile`s this person could help translate.
78
79 Similar to `getTranslatableFiles`, this method picks an
80 arbitrary series of `POFile`s that the user is not a reviewer
81 for and has not worked on recently, but which are in a language
82 the user works in.
83 """
8475
=== modified file 'lib/lp/translations/model/translationsperson.py'
--- lib/lp/translations/model/translationsperson.py 2012-10-30 15:54:00 +0000
+++ lib/lp/translations/model/translationsperson.py 2013-01-14 03:38:22 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the1# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -161,15 +161,9 @@
161 '(SELECT * FROM translatable_distroseries)'))).config(161 '(SELECT * FROM translatable_distroseries)'))).config(
162 distinct=True).order_by(POFile.date_changed)162 distinct=True).order_by(POFile.date_changed)
163163
164 def _queryTranslatableFiles(self, worked_on, no_older_than=None,164 def _queryTranslatableFiles(self, no_older_than=None, languages=None):
165 languages=None):
166 """Get `POFile`s this person could help translate.165 """Get `POFile`s this person could help translate.
167166
168 :param worked_on: If True, get `POFile`s that the person has
169 been working on recently (where "recently" is defined as
170 `no_older_than`). If False, get ones that the person has
171 not been working on recently (those that the person has
172 never worked on, or last worked on before `no_older_than`).
173 :param no_older_than: Oldest involvement to consider. If the167 :param no_older_than: Oldest involvement to consider. If the
174 person last worked on a `POFile` before this date, that168 person last worked on a `POFile` before this date, that
175 counts as not having worked on it.169 counts as not having worked on it.
@@ -179,18 +173,25 @@
179 if self.person.is_team:173 if self.person.is_team:
180 return []174 return []
181175
182 tables = self._composePOFileReviewerJoins(176 tables = self._composePOFileReviewerJoins(expect_reviewer_status=False)
183 expect_reviewer_status=False)177
184178 join_condition = And(
185 translator_join, translator_condition = (179 POFileTranslator.personID == self.person.id,
186 self._composePOFileTranslatorJoin(worked_on, no_older_than))180 POFileTranslator.pofileID == POFile.id,
181 POFile.language != getUtility(ILaunchpadCelebrities).english)
182
183 if no_older_than is not None:
184 join_condition = And(
185 join_condition,
186 POFileTranslator.date_last_touched >= no_older_than)
187
188 translator_join = Join(POFileTranslator, join_condition)
187 tables.append(translator_join)189 tables.append(translator_join)
188190
189 translated_count = (191 translated_count = (
190 POFile.currentcount + POFile.updatescount + POFile.rosettacount)192 POFile.currentcount + POFile.updatescount + POFile.rosettacount)
191193
192 conditions = And(194 conditions = translated_count < POTemplate.messagecount
193 translated_count < POTemplate.messagecount, translator_condition)
194195
195 # The person must not be a reviewer for this translation (unless196 # The person must not be a reviewer for this translation (unless
196 # it's in the sense that any user gets review permissions197 # it's in the sense that any user gets review permissions
@@ -219,12 +220,11 @@
219 if languages is not None:220 if languages is not None:
220 conditions = And(conditions, POFile.languageID.is_in(languages))221 conditions = And(conditions, POFile.languageID.is_in(languages))
221222
222 source = Store.of(self.person).using(*tables)223 return Store.of(self.person).using(*tables).find(POFile, conditions)
223 return source.find(POFile, conditions)
224224
225 def getTranslatableFiles(self, no_older_than=None, urgent_first=True):225 def getTranslatableFiles(self, no_older_than=None, urgent_first=True):
226 """See `ITranslationsPerson`."""226 """See `ITranslationsPerson`."""
227 results = self._queryTranslatableFiles(True, no_older_than)227 results = self._queryTranslatableFiles(no_older_than)
228228
229 translated_count = (229 translated_count = (
230 POFile.currentcount + POFile.updatescount + POFile.rosettacount)230 POFile.currentcount + POFile.updatescount + POFile.rosettacount)
@@ -234,16 +234,6 @@
234234
235 return results.order_by(ordering)235 return results.order_by(ordering)
236236
237 def suggestTranslatableFiles(self, no_older_than=None):
238 """See `ITranslationsPerson`."""
239 # XXX JeroenVermeulen 2009-08-28: Ideally this would also check
240 # for a free licence. That's hard to do in SQL though.
241 languages = set([
242 language.id for language in self.translatable_languages])
243 results = self._queryTranslatableFiles(
244 False, no_older_than, languages=languages)
245 return results.order_by(['random()'])
246
247 def _composePOFileReviewerCTEs(self, no_older_than):237 def _composePOFileReviewerCTEs(self, no_older_than):
248 """Compose Storm CTEs for common `POFile` queries.238 """Compose Storm CTEs for common `POFile` queries.
249239
@@ -405,42 +395,3 @@
405 TranslatorJoin,395 TranslatorJoin,
406 TranslationTeamJoin,396 TranslationTeamJoin,
407 ]397 ]
408
409 def _composePOFileTranslatorJoin(self, expected_presence,
410 no_older_than=None):
411 """Compose join condition for `POFileTranslator`.
412
413 Checks for a `POFileTranslator` record matching a `POFile` and
414 `Person` in a join.
415
416 :param expected_presence: whether the `POFileTranslator` record
417 is to be present, or absent. The join will enforce presence
418 through a regular inner join, or absence by an outer join
419 with a condition that the record not be present.
420 :param no_older_than: optional cutoff date. `POFileTranslator`
421 records older than this date are not considered.
422 :return: a tuple of the join, and a condition to be checked by
423 the query. Combine it with the query's other conditions
424 using `And`.
425 """
426 join_condition = And(
427 POFileTranslator.personID == self.person.id,
428 POFileTranslator.pofileID == POFile.id,
429 POFile.language != getUtility(ILaunchpadCelebrities).english)
430
431 if no_older_than is not None:
432 join_condition = And(
433 join_condition,
434 POFileTranslator.date_last_touched >= no_older_than)
435
436 if expected_presence:
437 # A regular inner join enforces this. No need for an extra
438 # condition; the join does it more efficiently.
439 return Join(POFileTranslator, join_condition), True
440 else:
441 # Check for absence. Usually the best way to check for this
442 # is an outer join plus a condition that the outer join
443 # match no record.
444 return (
445 LeftJoin(POFileTranslator, join_condition),
446 POFileTranslator.id == None)
447398
=== modified file 'lib/lp/translations/tests/test_potmsgset.py'
--- lib/lp/translations/tests/test_potmsgset.py 2013-01-07 02:40:55 +0000
+++ lib/lp/translations/tests/test_potmsgset.py 2013-01-14 03:38:22 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the1# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -408,7 +408,7 @@
408 self.assertContentEqual(408 self.assertContentEqual(
409 self.potmsgset.getExternallyUsedTranslationMessages(language),409 self.potmsgset.getExternallyUsedTranslationMessages(language),
410 [other_translation, current_translation])410 [other_translation, current_translation])
411 self.assertEquals(411 self.assertContentEqual(
412 self.potmsgset.getExternallySuggestedOrUsedTranslationMessages(412 self.potmsgset.getExternallySuggestedOrUsedTranslationMessages(
413 used_languages=[language])[language].used,413 used_languages=[language])[language].used,
414 [other_translation, current_translation])414 [other_translation, current_translation])