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
1=== modified file 'lib/lp/translations/browser/person.py'
2--- lib/lp/translations/browser/person.py 2012-10-30 15:54:00 +0000
3+++ lib/lp/translations/browser/person.py 2013-01-14 03:38:22 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Person-related translations view classes."""
10@@ -333,17 +333,6 @@
11
12 return TranslateLinksAggregator().aggregate(pofiles)
13
14- def _suggestTargetsForTranslation(self, max_fetch=None):
15- """Suggest translations this person could be helping complete."""
16- person = ITranslationsPerson(self.context)
17- pofiles = person.suggestTranslatableFiles(
18- no_older_than=self.history_horizon)
19-
20- if max_fetch is not None:
21- pofiles = pofiles[:max_fetch]
22-
23- return TranslateLinksAggregator().aggregate(pofiles)
24-
25 @cachedproperty
26 def all_projects_and_packages_to_review(self):
27 """Top projects and packages for this person to review."""
28@@ -408,10 +397,10 @@
29 """Suggest translations for this person to help complete."""
30 # Maximum number of translations to list that need the most work
31 # done.
32- max_urgent_targets = 3
33+ max_urgent_targets = 5
34 # Maximum number of translations to list that are almost
35 # complete.
36- max_almost_complete_targets = 3
37+ max_almost_complete_targets = 5
38 # Length of overall list to display.
39 list_length = 10
40
41@@ -426,11 +415,6 @@
42 overall, almost_complete, max_almost_complete_targets,
43 list_length)
44
45- fetch = 5 * (list_length - len(overall))
46- suggestions = self._suggestTargetsForTranslation(fetch)
47- overall = self._addToTargetsList(
48- overall, suggestions, list_length, list_length)
49-
50 return overall
51
52 to_complete_template = ViewPageTemplateFile(
53
54=== modified file 'lib/lp/translations/browser/tests/test_persontranslationview.py'
55--- lib/lp/translations/browser/tests/test_persontranslationview.py 2012-10-30 15:54:00 +0000
56+++ lib/lp/translations/browser/tests/test_persontranslationview.py 2013-01-14 03:38:22 +0000
57@@ -1,4 +1,4 @@
58-# Copyright 2009 Canonical Ltd. This software is licensed under the
59+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
60 # GNU Affero General Public License version 3 (see the file LICENSE).
61
62 __metaclass__ = type
63@@ -308,38 +308,9 @@
64 nonurgent_pofile.potemplate.productseries.product,
65 descriptions[0]['target'])
66
67- def test_suggestTargetsForTranslation(self):
68- # suggestTargetsForTranslation finds targets that the person
69- # could help translate.
70- previous_contrib = self._makePOFiles(1, previously_worked_on=True)
71- pofile = self._makePOFiles(1, previously_worked_on=False)[0]
72- self._addUntranslatedMessages(pofile, 1)
73-
74- descriptions = self.view._suggestTargetsForTranslation()
75-
76- self.assertEqual(1, len(descriptions))
77- self.assertEqual(
78- pofile.potemplate.productseries.product,
79- descriptions[0]['target'])
80-
81- def test_suggestTargetsForTranslation_limits_query(self):
82- # The max_fetch argument limits how many POFiles
83- # suggestTargetsForTranslation fetches.
84- previous_contrib = self._makePOFiles(1, previously_worked_on=True)
85- pofiles = self._makePOFiles(3, previously_worked_on=False)
86- for pofile in pofiles:
87- self._addUntranslatedMessages(pofile, 1)
88-
89- descriptions = self.view._suggestTargetsForTranslation(max_fetch=2)
90-
91- self.assertEqual(2, len(descriptions))
92- self.assertNotEqual(
93- descriptions[0]['target'], descriptions[1]['target'])
94-
95 def test_top_projects_and_packages_to_translate(self):
96 # top_projects_and_packages_to_translate lists targets that the
97- # user has worked on and could help translate, followed by
98- # randomly suggested ones that also need translation.
99+ # user has worked on and could help translate.
100 worked_on = self._makePOFiles(1, previously_worked_on=True)[0]
101 self._addUntranslatedMessages(worked_on, 1)
102 not_worked_on = self._makePOFiles(1, previously_worked_on=False)[0]
103@@ -347,16 +318,13 @@
104
105 descriptions = self.view.top_projects_and_packages_to_translate
106
107- self.assertEqual(2, len(descriptions))
108+ self.assertEqual(1, len(descriptions))
109 self.assertEqual(
110 worked_on.potemplate.productseries.product,
111 descriptions[0]['target'])
112- self.assertEqual(
113- not_worked_on.potemplate.productseries.product,
114- descriptions[1]['target'])
115
116 def test_top_p_n_p_to_translate_caps_existing_involvement(self):
117- # top_projects_and_packages_to_translate shows no more than 6
118+ # top_projects_and_packages_to_translate shows up to ten
119 # targets that the user has already worked on.
120 pofiles = self._makePOFiles(7, previously_worked_on=True)
121 for pofile in pofiles:
122@@ -364,12 +332,12 @@
123
124 descriptions = self.view.top_projects_and_packages_to_translate
125
126- self.assertEqual(6, len(descriptions))
127+ self.assertEqual(7, len(descriptions))
128
129 def test_top_p_n_p_to_translate_lists_most_and_least_translated(self):
130- # Of the maximum of 6 translations that the user has already
131- # worked on, the first 3 will be the ones with the most
132- # untranslated strings; the last 3 will have the fewest.
133+ # Of the maximum of ten translations that the user has already
134+ # worked on, the first 5 will be the ones with the most
135+ # untranslated strings; the last 5 will have the fewest.
136 # We create a lot more POFiles because internally the property
137 # will fetch many POFiles.
138 pofiles = self._makePOFiles(50, previously_worked_on=True)
139@@ -380,15 +348,15 @@
140
141 descriptions = self.view.top_projects_and_packages_to_translate
142
143- self.assertEqual(6, len(descriptions))
144+ self.assertEqual(10, len(descriptions))
145 targets = [item['target'] for item in descriptions]
146
147- # We happen to know that no more than 15 POFiles are fetched for
148- # each of the two categories, so the top 3 targets must be taken
149- # from the last 15 pofiles and the next 3 must be taken from the
150- # first 15 pofiles.
151- self.assertTrue(set(targets[:3]).issubset(products[15:]))
152- self.assertTrue(set(targets[3:]).issubset(products[:15]))
153+ # We happen to know that no more than 25 POFiles are fetched for
154+ # each of the two categories, so the top 5 targets must be taken
155+ # from the last 25 pofiles and the next 5 must be taken from the
156+ # first 25 pofiles.
157+ self.assertTrue(set(targets[:5]).issubset(products[25:]))
158+ self.assertTrue(set(targets[5:]).issubset(products[:25]))
159
160 # No target is mentioned more than once in the listing.
161 self.assertEqual(len(targets), len(set(targets)))
162
163=== modified file 'lib/lp/translations/interfaces/translationsperson.py'
164--- lib/lp/translations/interfaces/translationsperson.py 2012-10-29 06:13:44 +0000
165+++ lib/lp/translations/interfaces/translationsperson.py 2013-01-14 03:38:22 +0000
166@@ -1,4 +1,4 @@
167-# Copyright 2009 Canonical Ltd. This software is licensed under the
168+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
169 # GNU Affero General Public License version 3 (see the file LICENSE).
170
171 __metaclass__ = type
172@@ -72,12 +72,3 @@
173 :return: A query result of `POFile`s ordered by number of
174 untranslated messages.
175 """
176-
177- def suggestTranslatableFiles(no_older_than=None):
178- """Suggest `POFile`s this person could help translate.
179-
180- Similar to `getTranslatableFiles`, this method picks an
181- arbitrary series of `POFile`s that the user is not a reviewer
182- for and has not worked on recently, but which are in a language
183- the user works in.
184- """
185
186=== modified file 'lib/lp/translations/model/translationsperson.py'
187--- lib/lp/translations/model/translationsperson.py 2012-10-30 15:54:00 +0000
188+++ lib/lp/translations/model/translationsperson.py 2013-01-14 03:38:22 +0000
189@@ -1,4 +1,4 @@
190-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
191+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
192 # GNU Affero General Public License version 3 (see the file LICENSE).
193
194 __metaclass__ = type
195@@ -161,15 +161,9 @@
196 '(SELECT * FROM translatable_distroseries)'))).config(
197 distinct=True).order_by(POFile.date_changed)
198
199- def _queryTranslatableFiles(self, worked_on, no_older_than=None,
200- languages=None):
201+ def _queryTranslatableFiles(self, no_older_than=None, languages=None):
202 """Get `POFile`s this person could help translate.
203
204- :param worked_on: If True, get `POFile`s that the person has
205- been working on recently (where "recently" is defined as
206- `no_older_than`). If False, get ones that the person has
207- not been working on recently (those that the person has
208- never worked on, or last worked on before `no_older_than`).
209 :param no_older_than: Oldest involvement to consider. If the
210 person last worked on a `POFile` before this date, that
211 counts as not having worked on it.
212@@ -179,18 +173,25 @@
213 if self.person.is_team:
214 return []
215
216- tables = self._composePOFileReviewerJoins(
217- expect_reviewer_status=False)
218-
219- translator_join, translator_condition = (
220- self._composePOFileTranslatorJoin(worked_on, no_older_than))
221+ tables = self._composePOFileReviewerJoins(expect_reviewer_status=False)
222+
223+ join_condition = And(
224+ POFileTranslator.personID == self.person.id,
225+ POFileTranslator.pofileID == POFile.id,
226+ POFile.language != getUtility(ILaunchpadCelebrities).english)
227+
228+ if no_older_than is not None:
229+ join_condition = And(
230+ join_condition,
231+ POFileTranslator.date_last_touched >= no_older_than)
232+
233+ translator_join = Join(POFileTranslator, join_condition)
234 tables.append(translator_join)
235
236 translated_count = (
237 POFile.currentcount + POFile.updatescount + POFile.rosettacount)
238
239- conditions = And(
240- translated_count < POTemplate.messagecount, translator_condition)
241+ conditions = translated_count < POTemplate.messagecount
242
243 # The person must not be a reviewer for this translation (unless
244 # it's in the sense that any user gets review permissions
245@@ -219,12 +220,11 @@
246 if languages is not None:
247 conditions = And(conditions, POFile.languageID.is_in(languages))
248
249- source = Store.of(self.person).using(*tables)
250- return source.find(POFile, conditions)
251+ return Store.of(self.person).using(*tables).find(POFile, conditions)
252
253 def getTranslatableFiles(self, no_older_than=None, urgent_first=True):
254 """See `ITranslationsPerson`."""
255- results = self._queryTranslatableFiles(True, no_older_than)
256+ results = self._queryTranslatableFiles(no_older_than)
257
258 translated_count = (
259 POFile.currentcount + POFile.updatescount + POFile.rosettacount)
260@@ -234,16 +234,6 @@
261
262 return results.order_by(ordering)
263
264- def suggestTranslatableFiles(self, no_older_than=None):
265- """See `ITranslationsPerson`."""
266- # XXX JeroenVermeulen 2009-08-28: Ideally this would also check
267- # for a free licence. That's hard to do in SQL though.
268- languages = set([
269- language.id for language in self.translatable_languages])
270- results = self._queryTranslatableFiles(
271- False, no_older_than, languages=languages)
272- return results.order_by(['random()'])
273-
274 def _composePOFileReviewerCTEs(self, no_older_than):
275 """Compose Storm CTEs for common `POFile` queries.
276
277@@ -405,42 +395,3 @@
278 TranslatorJoin,
279 TranslationTeamJoin,
280 ]
281-
282- def _composePOFileTranslatorJoin(self, expected_presence,
283- no_older_than=None):
284- """Compose join condition for `POFileTranslator`.
285-
286- Checks for a `POFileTranslator` record matching a `POFile` and
287- `Person` in a join.
288-
289- :param expected_presence: whether the `POFileTranslator` record
290- is to be present, or absent. The join will enforce presence
291- through a regular inner join, or absence by an outer join
292- with a condition that the record not be present.
293- :param no_older_than: optional cutoff date. `POFileTranslator`
294- records older than this date are not considered.
295- :return: a tuple of the join, and a condition to be checked by
296- the query. Combine it with the query's other conditions
297- using `And`.
298- """
299- join_condition = And(
300- POFileTranslator.personID == self.person.id,
301- POFileTranslator.pofileID == POFile.id,
302- POFile.language != getUtility(ILaunchpadCelebrities).english)
303-
304- if no_older_than is not None:
305- join_condition = And(
306- join_condition,
307- POFileTranslator.date_last_touched >= no_older_than)
308-
309- if expected_presence:
310- # A regular inner join enforces this. No need for an extra
311- # condition; the join does it more efficiently.
312- return Join(POFileTranslator, join_condition), True
313- else:
314- # Check for absence. Usually the best way to check for this
315- # is an outer join plus a condition that the outer join
316- # match no record.
317- return (
318- LeftJoin(POFileTranslator, join_condition),
319- POFileTranslator.id == None)
320
321=== modified file 'lib/lp/translations/tests/test_potmsgset.py'
322--- lib/lp/translations/tests/test_potmsgset.py 2013-01-07 02:40:55 +0000
323+++ lib/lp/translations/tests/test_potmsgset.py 2013-01-14 03:38:22 +0000
324@@ -1,4 +1,4 @@
325-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
326+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
327 # GNU Affero General Public License version 3 (see the file LICENSE).
328
329 __metaclass__ = type
330@@ -408,7 +408,7 @@
331 self.assertContentEqual(
332 self.potmsgset.getExternallyUsedTranslationMessages(language),
333 [other_translation, current_translation])
334- self.assertEquals(
335+ self.assertContentEqual(
336 self.potmsgset.getExternallySuggestedOrUsedTranslationMessages(
337 used_languages=[language])[language].used,
338 [other_translation, current_translation])