Merge lp:~launchpad/launchpad/translation-sharing-status into lp:launchpad

Proposed by Henning Eggers
Status: Merged
Approved by: Henning Eggers
Approved revision: no longer in the source branch.
Merged at revision: 12606
Proposed branch: lp:~launchpad/launchpad/translation-sharing-status
Merge into: lp:launchpad
Diff against target: 802 lines (+703/-8)
6 files modified
lib/lp/testing/factory.py (+3/-3)
lib/lp/translations/browser/configure.zcml (+7/-0)
lib/lp/translations/browser/productseries.py (+3/-1)
lib/lp/translations/browser/sourcepackage.py (+132/-4)
lib/lp/translations/browser/tests/test_sharing_details.py (+437/-0)
lib/lp/translations/templates/sourcepackage-sharing-details.pt (+121/-0)
To merge this branch: bzr merge lp:~launchpad/launchpad/translation-sharing-status
Reviewer Review Type Date Requested Status
Curtis Hovey (community) ui Approve
Henning Eggers (community) Abstain
Leonard Richardson (community) Approve
Review via email: mp+53419@code.launchpad.net

Commit message

[r=leonardr][ui=sinzui][bug=732633] Added translation sharing details page.

Description of the change

Details
=======

This branch adds the new +sharing-details page that displays information about the sharing configuration and the current sharing state for each sharing template in a source package.

Here are some screenshots of the page:
http://people.canonical.com/~henninge/screenshots/sharing-details-page-unconfigured.png
http://people.canonical.com/~henninge/screenshots/sharing-details-page-incomplete.png
http://people.canonical.com/~henninge/screenshots/sharing-details-page-notice.png
http://people.canonical.com/~henninge/screenshots/sharing-details-page.png

The first part is a checklist that can be followed to set up upstream translation sharing. It indicates if each step has been completed an provides links to the respective places on Launchpad where these configurations can be performed.

The second part is a table of all templates in both the sourcepackage and the upstream project. The information is useful for maintainers to have an overview if all templates are sharing properly and if any of them may need updating.

Implementation details
----------------------

We (adeuring, heninge) worked on this branch together, so it is owned by ~launchpad. Will be interesting to see how that works through our merge machinery ...

The edit links in the checklist use menu items from the upstream product series. Thus the icon as well as the permissions are as they are defined in that menu. One item did not have an icon, so this was added.

The rest of the implementation is pretty straight forward: view class, template, page test. The view is hidden behind a feature flag, it will raise NotFound if the flag is not set.

Test
----

bin/test -vvvcm lp.translations.browser.tests.test_sharing_details

To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

I only have minor suggestions. I was able to follow the logic of the code, but I don't have a good grasp of how these parts of Launchpad (translation templates, source packages, distro series) work together, so I may have missed something. Feel free to ask for another review.

* You have some copy-and-paste comments on lines 398, 405, and 413.
* On line 543, the docstring reads like a commit message. you should say what you're actually setting up. You may also want to refactor your calls to getViewBrowser, but that's not a big deal.
* User-visible typo on line 753, "Translations are enable" -> "Translations are enabled"

review: Approve
Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Henning.

The think the configuration progress is good. I am surprised by the table headings though. Why are the subordinate headings a different colour? Is this because some can be sorted?

review: Needs Fixing (ui)
Revision history for this message
Curtis Hovey (sinzui) wrote :

Sorry Henning.

I do not think you need to change anything. I just asked a question.

review: Needs Information (ui)
Revision history for this message
Henning Eggers (henninge) wrote :

Thank you for your review! I fixed the issues and also put the getViewBrowser
repetitions in a method. That was something I had already been considering.

1=== modified file 'lib/lp/translations/browser/tests/test_sharing_details.py'
2--- lib/lp/translations/browser/tests/test_sharing_details.py 2011-03-15 10:48:28 +0000
3+++ lib/lp/translations/browser/tests/test_sharing_details.py 2011-03-15 18:58:11 +0000
4@@ -156,14 +156,14 @@
5 self.assertFalse(self.view.is_upstream_synchronization_enabled)
6
7 def test_is_upstream_synchronization_enabled__no_import(self):
8- # If the source package is not linked to an upstream series,
9+ # If no synchronization is enabled on the upstream series,
10 # is_upstream_synchronization_enabled returns False.
11 self.configureSharing(
12 translation_import_mode=TranslationsBranchImportMode.NO_IMPORT)
13 self.assertFalse(self.view.is_upstream_synchronization_enabled)
14
15 def test_is_upstream_synchronization_enabled__import_templates(self):
16- # If the source package is not linked to an upstream series,
17+ # If only template synchronization is enabled on the upstream series,
18 # is_upstream_synchronization_enabled returns False.
19 self.configureSharing(
20 translation_import_mode=
21@@ -171,8 +171,8 @@
22 self.assertFalse(self.view.is_upstream_synchronization_enabled)
23
24 def test_is_upstream_synchronization_enabled__import_translations(self):
25- # If the source package is not linked to an upstream series,
26- # is_upstream_synchronization_enabled returns False.
27+ # If full translation synchronization is enabled on the upstream
28+ # series, is_upstream_synchronization_enabled returns False.
29 self.configureSharing(
30 translation_import_mode=
31 TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
32@@ -301,7 +301,7 @@
33 return self.factory.makeSourcePackage(distroseries=distroseries)
34
35 def _makeFullyConfiguredSharing(self):
36- """Remove some redundant code from the tests."""
37+ """Setup a fully configured sharing scenario."""
38 packaging = self.factory.makePackagingLink(in_ubuntu=True)
39 productseries = packaging.productseries
40 sourcepackage = packaging.sourcepackage
41@@ -313,12 +313,15 @@
42 TranslationsBranchImportMode.IMPORT_TRANSLATIONS))
43 return (sourcepackage, productseries)
44
45+ def _getSharingDetailsViewBrowser(self, sourcepackage):
46+ return self.getViewBrowser(
47+ sourcepackage, no_login=True, rootsite="translations",
48+ view_name="+sharing-details")
49+
50 def test_checklist_unconfigured(self):
51 # Without a packaging link, sharing is completely unconfigured
52 sourcepackage = self._makeSourcePackage()
53- browser = self.getViewBrowser(
54- sourcepackage, no_login=True, rootsite="translations",
55- view_name="+sharing-details")
56+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
57 checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
58 self.assertIsNot(None, checklist)
59 self.assertTextMatchesExpressionIgnoreWhitespace("""
60@@ -332,9 +335,7 @@
61 def test_checklist_partly_configured(self):
62 # Linking a source package takes care of one item.
63 packaging = self.factory.makePackagingLink(in_ubuntu=True)
64- browser = self.getViewBrowser(
65- packaging.sourcepackage, no_login=True, rootsite="translations",
66- view_name="+sharing-details")
67+ browser = self._getSharingDetailsViewBrowser(packaging.sourcepackage)
68 checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
69 self.assertIsNot(None, checklist)
70 self.assertTextMatchesExpressionIgnoreWhitespace("""
71@@ -349,9 +350,7 @@
72 def test_checklist_fully_configured(self):
73 # A fully configured sharing setup.
74 sourcepackage = self._makeFullyConfiguredSharing()[0]
75- browser = self.getViewBrowser(
76- sourcepackage, no_login=True, rootsite="translations",
77- view_name="+sharing-details")
78+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
79 checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
80 self.assertIsNot(None, checklist)
81 self.assertTextMatchesExpressionIgnoreWhitespace("""
82@@ -359,7 +358,7 @@
83 Linked upstream series is .+ trunk series.
84 Change upstream link Remove upstream link
85 Upstream source branch is .+[.]
86- Translations are enable on the upstream project.
87+ Translations are enabled on the upstream project.
88 Automatic synchronization of translations is enabled.""",
89 extract_text(checklist))
90
91@@ -368,9 +367,7 @@
92 sourcepackage = self._makeSourcePackage()
93 self.factory.makePOTemplate(
94 name='foo-template', sourcepackage=sourcepackage)
95- browser = self.getViewBrowser(
96- sourcepackage, no_login=True, rootsite="translations",
97- view_name="+sharing-details")
98+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
99 tbody = find_tag_by_id(
100 browser.contents, 'template-table').find('tbody')
101 self.assertIsNot(None, tbody)
102@@ -386,9 +383,7 @@
103 name=template_name, sourcepackage=sourcepackage)
104 self.factory.makePOTemplate(
105 name=template_name, productseries=productseries)
106- browser = self.getViewBrowser(
107- sourcepackage, no_login=True, rootsite="translations",
108- view_name="+sharing-details")
109+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
110 tbody = find_tag_by_id(
111 browser.contents, 'template-table').find('tbody')
112 self.assertIsNot(None, tbody)
113@@ -404,9 +399,7 @@
114 template_name = 'foo-template'
115 self.factory.makePOTemplate(
116 name=template_name, productseries=productseries)
117- browser = self.getViewBrowser(
118- sourcepackage, no_login=True, rootsite="translations",
119- view_name="+sharing-details")
120+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
121 tbody = find_tag_by_id(
122 browser.contents, 'template-table').find('tbody')
123 self.assertIsNot(None, tbody)
124@@ -418,9 +411,7 @@
125 # When sharing is fully configured but no upstream templates are
126 # found, a message is displayed.
127 sourcepackage = self._makeFullyConfiguredSharing()[0]
128- browser = self.getViewBrowser(
129- sourcepackage, no_login=True, rootsite="translations",
130- view_name="+sharing-details")
131+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
132 self.assertEqual(
133 ["No upstream templates have been found yet. Please follow "
134 "the import process by going to the Translation Import Queue "
135@@ -432,9 +423,7 @@
136 # message should be displayed.
137 sourcepackage, productseries = self._makeFullyConfiguredSharing()
138 self.factory.makePOTemplate(productseries=productseries)
139- browser = self.getViewBrowser(
140- sourcepackage, no_login=True, rootsite="translations",
141- view_name="+sharing-details")
142+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
143 self.assertEqual([], get_feedback_messages(browser.contents))
144
145 def test_no_message_with_incomplate_sharing(self):
146@@ -444,7 +433,5 @@
147 productseries = packaging.productseries
148 sourcepackage = packaging.sourcepackage
149 self.factory.makePOTemplate(productseries=productseries)
150- browser = self.getViewBrowser(
151- sourcepackage, no_login=True, rootsite="translations",
152- view_name="+sharing-details")
153+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
154 self.assertEqual([], get_feedback_messages(browser.contents))
155
156=== modified file 'lib/lp/translations/templates/sourcepackage-sharing-details.pt'
157--- lib/lp/translations/templates/sourcepackage-sharing-details.pt 2011-03-14 15:43:14 +0000
158+++ lib/lp/translations/templates/sourcepackage-sharing-details.pt 2011-03-15 18:45:49 +0000
159@@ -56,7 +56,7 @@
160 </li>
161 <li class="sprite yes"
162 tal:condition="view/is_upstream_translations_enabled">
163- Translations are enable on the upstream project.
164+ Translations are enabled on the upstream project.
165 <a tal:condition="view/is_packaging_configured"
166 tal:replace="structure context/productseries/product/menu:translations/settings/fmt:icon" />
167 </li>
Revision history for this message
Henning Eggers (henninge) wrote :

> The think the configuration progress is good. I am surprised by the table
> headings though. Why are the subordinate headings a different colour? Is this
> because some can be sorted?

Yes, the color difference is introduced by the "sortable" class but that does not play well with subordinate headings anyway, so I removed it. All headings are black now.

Revision history for this message
Henning Eggers (henninge) :
review: Abstain
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you Henning.

I think this is good to land.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/testing/factory.py'
2--- lib/lp/testing/factory.py 2011-03-11 04:11:40 +0000
3+++ lib/lp/testing/factory.py 2011-03-15 19:18:37 +0000
4@@ -1080,10 +1080,10 @@
5 if sourcepackagename is None or isinstance(sourcepackagename, str):
6 sourcepackagename = self.makeSourcePackageName(sourcepackagename)
7 if distroseries is None:
8- distribution = None
9 if in_ubuntu:
10- distribution = getUtility(ILaunchpadCelebrities).ubuntu
11- distroseries = self.makeDistroSeries(distribution=distribution)
12+ distroseries = self.makeUbuntuDistroSeries()
13+ else:
14+ distroseries = self.makeDistroSeries()
15 if packaging_type is None:
16 packaging_type = PackagingType.PRIME
17 if owner is None:
18
19=== modified file 'lib/lp/translations/browser/configure.zcml'
20--- lib/lp/translations/browser/configure.zcml 2011-02-14 04:19:37 +0000
21+++ lib/lp/translations/browser/configure.zcml 2011-03-15 19:18:37 +0000
22@@ -605,6 +605,13 @@
23 for="lp.registry.interfaces.sourcepackage.ISourcePackage"
24 class="lp.translations.browser.translations.TranslationsRedirectView"
25 permission="zope.Public"/>
26+ <browser:page
27+ for="lp.registry.interfaces.sourcepackage.ISourcePackage"
28+ name="+sharing-details"
29+ class="lp.translations.browser.sourcepackage.SourcePackageTranslationSharingDetailsView"
30+ permission="zope.Public"
31+ template="../templates/sourcepackage-sharing-details.pt"
32+ layer="lp.translations.publisher.TranslationsLayer"/>
33 <browser:pages
34 for="lp.registry.interfaces.sourcepackage.ISourcePackage"
35 permission="zope.Public"
36
37=== modified file 'lib/lp/translations/browser/productseries.py'
38--- lib/lp/translations/browser/productseries.py 2011-03-02 17:49:15 +0000
39+++ lib/lp/translations/browser/productseries.py 2011-03-15 19:18:37 +0000
40@@ -85,7 +85,9 @@
41 @enabled_with_permission('launchpad.Edit')
42 def settings(self):
43 """Return a link to configure the translations settings."""
44- return Link('+translations-settings', 'Settings', site='translations')
45+ return Link(
46+ '+translations-settings', 'Settings',
47+ site='translations', icon='edit')
48
49 @enabled_with_permission('launchpad.Edit')
50 def requestbzrimport(self):
51
52=== modified file 'lib/lp/translations/browser/sourcepackage.py'
53--- lib/lp/translations/browser/sourcepackage.py 2011-03-08 09:59:36 +0000
54+++ lib/lp/translations/browser/sourcepackage.py 2011-03-15 19:18:37 +0000
55@@ -1,4 +1,4 @@
56-# Copyright 2009 Canonical Ltd. This software is licensed under the
57+# Copyright 2011 Canonical Ltd. This software is licensed under the
58 # GNU Affero General Public License version 3 (see the file LICENSE).
59
60 """Browser views for translation pages for sourcepackages."""
61@@ -8,8 +8,11 @@
62 __all__ = [
63 'SourcePackageTranslationsExportView',
64 'SourcePackageTranslationsView',
65+ 'SourcePackageTranslationSharingStatus',
66 ]
67
68+from zope.publisher.interfaces import NotFound
69+
70 from canonical.launchpad.webapp import (
71 canonical_url,
72 enabled_with_permission,
73@@ -17,19 +20,33 @@
74 NavigationMenu,
75 )
76 from canonical.launchpad.webapp.authorization import check_permission
77+from canonical.launchpad.webapp.menu import structured
78+from canonical.launchpad.webapp.publisher import LaunchpadView
79+from lp.app.enums import ServiceUsage
80 from lp.registry.interfaces.sourcepackage import ISourcePackage
81+from lp.services.features import getFeatureFlag
82 from lp.translations.browser.poexportrequest import BaseExportView
83 from lp.translations.browser.translations import TranslationsMixin
84 from lp.translations.browser.translationsharing import (
85 TranslationSharingDetailsMixin,
86 )
87+from lp.translations.interfaces.translations import (
88+ TranslationsBranchImportMode,
89+ )
90 from lp.translations.utilities.translationsharinginfo import (
91 has_upstream_template,
92 get_upstream_sharing_info,
93 )
94
95
96+class SharingDetailsPermissionsMixin:
97+
98+ def can_edit_sharing_details(self):
99+ return check_permission('launchpad.Edit', self.context.distroseries)
100+
101+
102 class SourcePackageTranslationsView(TranslationsMixin,
103+ SharingDetailsPermissionsMixin,
104 TranslationSharingDetailsMixin):
105
106 @property
107@@ -56,9 +73,6 @@
108 """See `TranslationSharingDetailsMixin`."""
109 return self.context
110
111- def can_edit_sharing_details(self):
112- return check_permission('launchpad.Edit', self.context.distroseries)
113-
114
115 class SourcePackageTranslationsMenu(NavigationMenu):
116 usedfor = ISourcePackage
117@@ -100,3 +114,117 @@
118 @property
119 def label(self):
120 return "Download translations for %s" % self.download_description
121+
122+
123+class SourcePackageTranslationSharingDetailsView(
124+ LaunchpadView,
125+ SharingDetailsPermissionsMixin):
126+ """Details about translation sharing."""
127+
128+ page_title = "Sharing details"
129+
130+ def initialize(self):
131+ if not getFeatureFlag('translations.sharing_information.enabled'):
132+ raise NotFound(self.context, '+sharing-details')
133+ super(SourcePackageTranslationSharingDetailsView, self).initialize()
134+ has_no_upstream_templates = (
135+ self.is_configuration_complete and
136+ not has_upstream_template(self.context))
137+ if has_no_upstream_templates:
138+ self.request.response.addInfoNotification(
139+ structured(
140+ 'No upstream templates have been found yet. Please follow '
141+ 'the import process by going to the '
142+ '<a href="%s">Translation Import Queue</a> of the '
143+ 'upstream project series.' %(
144+ canonical_url(
145+ self.context.productseries, rootsite='translations',
146+ view_name="+imports"))))
147+
148+ @property
149+ def is_packaging_configured(self):
150+ """Is a packaging link defined for this branch?"""
151+ return self.context.direct_packaging is not None
152+
153+ @property
154+ def no_item_class(self):
155+ """CSS class for 'no' items."""
156+ css_class = "sprite no"
157+ if self.is_packaging_configured:
158+ return css_class
159+ else:
160+ return css_class + " lowlight"
161+
162+ @property
163+ def has_upstream_branch(self):
164+ """Does the upstream series have a source code branch?"""
165+ if not self.is_packaging_configured:
166+ return False
167+ return self.context.direct_packaging.productseries.branch is not None
168+
169+ @property
170+ def is_upstream_translations_enabled(self):
171+ """Are Launchpad translations enabled for the upstream series?"""
172+ if not self.is_packaging_configured:
173+ return False
174+ product = self.context.direct_packaging.productseries.product
175+ return product.translations_usage in (
176+ ServiceUsage.LAUNCHPAD, ServiceUsage.EXTERNAL)
177+
178+ @property
179+ def is_upstream_synchronization_enabled(self):
180+ """Is automatic synchronization of upstream translations enabled?"""
181+ if not self.is_packaging_configured:
182+ return False
183+ series = self.context.direct_packaging.productseries
184+ return (
185+ series.translations_autoimport_mode ==
186+ TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
187+
188+ @property
189+ def is_configuration_complete(self):
190+ """Is anything missing in the set up for translation sharing?"""
191+ # A check if the required packaging link exists is implicitly
192+ # done in the implementation of the other properties.
193+ return (
194+ self.has_upstream_branch and
195+ self.is_upstream_translations_enabled and
196+ self.is_upstream_synchronization_enabled)
197+
198+ def template_info(self):
199+ """Details about translation templates.
200+
201+ :return: A list of dictionaries containing details about each
202+ template. Each dictionary contains:
203+ 'name': The name of the template
204+ 'package_template': The package template (may be None)
205+ 'upstream_template': The corresponding upstream template
206+ (may be None)
207+ 'status': one of the string 'linking', 'shared',
208+ 'only in Ubuntu', 'only in upstream'
209+ """
210+ info = {}
211+ templates_on_this_side = self.context.getCurrentTranslationTemplates()
212+ for template in templates_on_this_side:
213+ info[template.name] = {
214+ 'name': template.name,
215+ 'package_template': template,
216+ 'upstream_template': None,
217+ 'status': 'only in Ubuntu',
218+ }
219+ if self.is_configuration_complete:
220+ upstream_templates = (
221+ self.context.productseries.getCurrentTranslationTemplates())
222+ for template in upstream_templates:
223+ if template.name in info:
224+ info[template.name]['upstream_template'] = template
225+ info[template.name]['status'] = 'shared'
226+ else:
227+ info[template.name] = {
228+ 'name': template.name,
229+ 'package_template': None,
230+ 'upstream_template': template,
231+ 'status': 'only in upstream',
232+ }
233+ info = info.values()
234+ return sorted(info, key=lambda template: template['name'])
235
236=== added file 'lib/lp/translations/browser/tests/test_sharing_details.py'
237--- lib/lp/translations/browser/tests/test_sharing_details.py 1970-01-01 00:00:00 +0000
238+++ lib/lp/translations/browser/tests/test_sharing_details.py 2011-03-15 19:18:37 +0000
239@@ -0,0 +1,437 @@
240+# Copyright 2011 Canonical Ltd. This software is licensed under the
241+# GNU Affero General Public License version 3 (see the file LICENSE).
242+
243+__metaclass__ = type
244+
245+from canonical.launchpad.testing.pages import (
246+ extract_text,
247+ find_tag_by_id,
248+ get_feedback_messages,
249+ )
250+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
251+from canonical.testing.layers import (
252+ DatabaseFunctionalLayer,
253+ )
254+from lp.app.enums import ServiceUsage
255+from lp.services.features.testing import FeatureFixture
256+from lp.testing import (
257+ BrowserTestCase,
258+ person_logged_in,
259+ TestCaseWithFactory,
260+ )
261+from lp.translations.browser.sourcepackage import (
262+ SourcePackageTranslationSharingDetailsView,
263+ )
264+from lp.translations.interfaces.translations import (
265+ TranslationsBranchImportMode,
266+ )
267+
268+
269+class ConfigureUpstreamProjectMixin:
270+ """Provide a method for project configuration."""
271+
272+ def configureUpstreamProject(self, productseries,
273+ set_upstream_branch=False,
274+ translations_usage=ServiceUsage.UNKNOWN,
275+ translation_import_mode=TranslationsBranchImportMode.NO_IMPORT):
276+ """Configure the productseries and its product as an upstream project.
277+ """
278+ with person_logged_in(productseries.product.owner):
279+ if set_upstream_branch:
280+ productseries.branch = self.factory.makeBranch(
281+ product=productseries.product)
282+ productseries.product.translations_usage = translations_usage
283+ productseries.translations_autoimport_mode = (
284+ translation_import_mode)
285+
286+
287+class TestSourcePackageTranslationSharingDetailsView(TestCaseWithFactory,
288+ ConfigureUpstreamProjectMixin):
289+ """Tests for SourcePackageTranslationSharingStatus."""
290+
291+ layer = DatabaseFunctionalLayer
292+
293+ def setUp(self):
294+ super(TestSourcePackageTranslationSharingDetailsView, self).setUp()
295+ self.useFixture(FeatureFixture(
296+ {'translations.sharing_information.enabled': 'on'}))
297+ distroseries = self.factory.makeUbuntuDistroSeries()
298+ self.sourcepackage = self.factory.makeSourcePackage(
299+ distroseries=distroseries)
300+ self.ubuntu_only_template = self.factory.makePOTemplate(
301+ sourcepackage=self.sourcepackage, name='ubuntu-only')
302+ self.shared_template_ubuntu_side = self.factory.makePOTemplate(
303+ sourcepackage=self.sourcepackage, name='shared-template')
304+ self.productseries = self.factory.makeProductSeries()
305+ self.shared_template_upstream_side = self.factory.makePOTemplate(
306+ productseries=self.productseries, name='shared-template')
307+ self.upstream_only_template = self.factory.makePOTemplate(
308+ productseries=self.productseries, name='upstream-only')
309+ self.view = SourcePackageTranslationSharingDetailsView(
310+ self.sourcepackage, LaunchpadTestRequest())
311+ self.view.initialize()
312+
313+ def configureSharing(self,
314+ set_upstream_branch=False,
315+ translations_usage=ServiceUsage.UNKNOWN,
316+ translation_import_mode=TranslationsBranchImportMode.NO_IMPORT):
317+ """Configure translation sharing, at least partially.
318+
319+ A packaging link is always set; the remaining configuration is
320+ done only if explicitly specified.
321+ """
322+ self.sourcepackage.setPackaging(
323+ self.productseries, self.productseries.owner)
324+ self.configureUpstreamProject(
325+ self.productseries, set_upstream_branch, translations_usage,
326+ translation_import_mode)
327+
328+ def test_is_packaging_configured__not_configured(self):
329+ # If a sourcepackage is not linked to a product series,
330+ # SourcePackageTranslationSharingStatus.is_packaging_configured
331+ # returns False.
332+ self.assertFalse(self.view.is_packaging_configured)
333+
334+ def test_is_packaging_configured__configured(self):
335+ # If a sourcepackage is linked to a product series,
336+ # SourcePackageTranslationSharingStatus.is_packaging_configured
337+ # returns True.
338+ self.configureSharing()
339+ self.assertTrue(self.view.is_packaging_configured)
340+
341+ def test_has_upstream_branch__no_packaging_link(self):
342+ # If the source package is not linked to an upstream series,
343+ # SourcePackageTranslationSharingStatus.has_upstream_branch
344+ # returns False.
345+ self.assertFalse(self.view.has_upstream_branch)
346+
347+ def test_has_upstream_branch__no_branch_exists(self):
348+ # If the upstream product series does not have any source
349+ # code branch,
350+ # SourcePackageTranslationSharingStatus.has_upstream_branch
351+ # returns False.
352+ self.configureSharing()
353+ self.assertFalse(self.view.has_upstream_branch)
354+
355+ def test_has_upstream_branch__branch_exists(self):
356+ # If the upstream product series has at least one source
357+ # code branch,
358+ # SourcePackageTranslationSharingStatus.has_upstream_branch
359+ # returns True.
360+ self.configureSharing(set_upstream_branch=True)
361+ self.assertTrue(self.view.has_upstream_branch)
362+
363+ def test_is_upstream_translations_enabled__no_packaging_link(self):
364+ # If the source package is not linked to an upstream series,
365+ # is_upstream_translations_enabled returns False.
366+ self.assertFalse(self.view.is_upstream_translations_enabled)
367+
368+ def test_is_upstream_translations_enabled__when_unknown(self):
369+ # If it is unknown what the upstream project uses for
370+ # translations, is_upstream_translations_enabled returns False.
371+ self.configureSharing(translations_usage=ServiceUsage.UNKNOWN)
372+ self.assertFalse(self.view.is_upstream_translations_enabled)
373+
374+ def test_is_upstream_translations_enabled__when_launchpad(self):
375+ # If the upstream product series uses Launchpad for
376+ # translations, is_upstream_translations_enabled returns True.
377+ self.configureSharing(translations_usage=ServiceUsage.LAUNCHPAD)
378+ self.assertTrue(self.view.is_upstream_translations_enabled)
379+
380+ def test_is_upstream_translations_enabled__when_external(self):
381+ # If the upstream product series uses an external tool for
382+ # translations, is_upstream_translations_enabled returns True.
383+ self.configureSharing(translations_usage=ServiceUsage.EXTERNAL)
384+ self.assertTrue(self.view.is_upstream_translations_enabled)
385+
386+ def test_is_upstream_translations_enabled__when_not_applicable(self):
387+ # If the upstream product series does not do translations at all,
388+ # is_upstream_translations_enabled returns False.
389+ self.configureSharing(translations_usage=ServiceUsage.NOT_APPLICABLE)
390+ self.assertFalse(self.view.is_upstream_translations_enabled)
391+
392+ def test_is_upstream_synchronization_enabled__no_packaging_link(self):
393+ # If the source package is not linked to an upstream series,
394+ # is_upstream_synchronization_enabled returns False.
395+ self.assertFalse(self.view.is_upstream_synchronization_enabled)
396+
397+ def test_is_upstream_synchronization_enabled__no_import(self):
398+ # If no synchronization is enabled on the upstream series,
399+ # is_upstream_synchronization_enabled returns False.
400+ self.configureSharing(
401+ translation_import_mode=TranslationsBranchImportMode.NO_IMPORT)
402+ self.assertFalse(self.view.is_upstream_synchronization_enabled)
403+
404+ def test_is_upstream_synchronization_enabled__import_templates(self):
405+ # If only template synchronization is enabled on the upstream series,
406+ # is_upstream_synchronization_enabled returns False.
407+ self.configureSharing(
408+ translation_import_mode=
409+ TranslationsBranchImportMode.IMPORT_TEMPLATES)
410+ self.assertFalse(self.view.is_upstream_synchronization_enabled)
411+
412+ def test_is_upstream_synchronization_enabled__import_translations(self):
413+ # If full translation synchronization is enabled on the upstream
414+ # series, is_upstream_synchronization_enabled returns False.
415+ self.configureSharing(
416+ translation_import_mode=
417+ TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
418+ self.assertTrue(self.view.is_upstream_synchronization_enabled)
419+
420+ def test_is_configuration_complete__nothing_configured(self):
421+ # If none of the conditions for translation sharing are
422+ # fulfilled (the default test setup), is_configuration_complete
423+ # is False.
424+ self.assertFalse(self.view.is_configuration_complete)
425+
426+ def test_is_configuration_complete__only_packaging_set(self):
427+ # If the packaging link is set but the other conditions for
428+ # translation sharing are not fulfilled, is_configuration_complete
429+ # is False.
430+ self.configureSharing()
431+ self.assertFalse(self.view.is_configuration_complete)
432+
433+ def test_is_configuration_complete__packaging_upstream_branch_set(self):
434+ # If the packaging link is set and if an upstream branch is
435+ # configuerd but if the other conditions are not fulfilled,
436+ # is_configuration_complete is False.
437+ self.configureSharing(set_upstream_branch=True)
438+ self.assertFalse(self.view.is_configuration_complete)
439+
440+ def test_is_configuration_complete__packaging_transl_enabled(self):
441+ # If the packaging link is set and if an upstream series
442+ # uses Launchpad translations but if the other conditions
443+ # are not fulfilled, is_configuration_complete is False.
444+ self.configureSharing(translations_usage=ServiceUsage.LAUNCHPAD)
445+ self.assertFalse(self.view.is_configuration_complete)
446+
447+ def test_is_configuration_complete__no_auto_sync(self):
448+ # If
449+ # - a packaging link is set
450+ # - a branch is set for the upstream series
451+ # - the upstream series uses Launchpad translations
452+ # but if the upstream series does not synchronize translations
453+ # then is_configuration_complete is False.
454+ self.configureSharing(
455+ set_upstream_branch=True,
456+ translations_usage=ServiceUsage.LAUNCHPAD)
457+ self.assertFalse(self.view.is_configuration_complete)
458+
459+ def test_is_configuration_complete__all_conditions_fulfilled(self):
460+ # If
461+ # - a packaging link is set
462+ # - a branch is set for the upstream series
463+ # - the upstream series uses Launchpad translations
464+ # - the upstream series synchronizes translations
465+ # then is_configuration_complete is True.
466+ self.configureSharing(
467+ set_upstream_branch=True,
468+ translations_usage=ServiceUsage.LAUNCHPAD,
469+ translation_import_mode=
470+ TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
471+ self.assertTrue(self.view.is_configuration_complete)
472+
473+ def test_template_info__no_sharing(self):
474+ # If translation sharing is not configured,
475+ # SourcePackageTranslationSharingDetailsView.info returns
476+ # only data about templates in Ubuntu.
477+ expected = [
478+ {
479+ 'name': 'shared-template',
480+ 'status': 'only in Ubuntu',
481+ 'package_template': self.shared_template_ubuntu_side,
482+ 'upstream_template': None,
483+ },
484+ {
485+ 'name': 'ubuntu-only',
486+ 'status': 'only in Ubuntu',
487+ 'package_template': self.ubuntu_only_template,
488+ 'upstream_template': None,
489+ },
490+ ]
491+ self.assertEqual(expected, self.view.template_info())
492+
493+ def test_template_info___sharing(self):
494+ # If translation sharing is configured,
495+ # SourcePackageTranslationSharingDetailsView.info returns
496+ # only data about templates in Ubuntu and about upstream
497+ # templates.
498+ self.configureSharing(
499+ set_upstream_branch=True,
500+ translations_usage=ServiceUsage.LAUNCHPAD,
501+ translation_import_mode=
502+ TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
503+ expected = [
504+ {
505+ 'name': 'shared-template',
506+ 'status': 'shared',
507+ 'package_template': self.shared_template_ubuntu_side,
508+ 'upstream_template': self.shared_template_upstream_side,
509+ },
510+ {
511+ 'name': 'ubuntu-only',
512+ 'status': 'only in Ubuntu',
513+ 'package_template': self.ubuntu_only_template,
514+ 'upstream_template': None,
515+ },
516+ {
517+ 'name': 'upstream-only',
518+ 'status': 'only in upstream',
519+ 'package_template': None,
520+ 'upstream_template': self.upstream_only_template,
521+ },
522+ ]
523+ self.assertEqual(expected, self.view.template_info())
524+
525+
526+class TestSourcePackageSharingDetailsPage(BrowserTestCase,
527+ ConfigureUpstreamProjectMixin):
528+ """Test for the sharing details page of a source package."""
529+
530+ layer = DatabaseFunctionalLayer
531+
532+ def setUp(self):
533+ super(TestSourcePackageSharingDetailsPage, self).setUp()
534+ self.useFixture(FeatureFixture(
535+ {'translations.sharing_information.enabled': 'on'}))
536+
537+ def _makeSourcePackage(self):
538+ """Make a source package in Ubuntu."""
539+ distroseries = self.factory.makeUbuntuDistroSeries()
540+ return self.factory.makeSourcePackage(distroseries=distroseries)
541+
542+ def _makeFullyConfiguredSharing(self):
543+ """Setup a fully configured sharing scenario."""
544+ packaging = self.factory.makePackagingLink(in_ubuntu=True)
545+ productseries = packaging.productseries
546+ sourcepackage = packaging.sourcepackage
547+ self.configureUpstreamProject(
548+ productseries,
549+ set_upstream_branch=True,
550+ translations_usage=ServiceUsage.LAUNCHPAD,
551+ translation_import_mode=(
552+ TranslationsBranchImportMode.IMPORT_TRANSLATIONS))
553+ return (sourcepackage, productseries)
554+
555+ def _getSharingDetailsViewBrowser(self, sourcepackage):
556+ return self.getViewBrowser(
557+ sourcepackage, no_login=True, rootsite="translations",
558+ view_name="+sharing-details")
559+
560+ def test_checklist_unconfigured(self):
561+ # Without a packaging link, sharing is completely unconfigured
562+ sourcepackage = self._makeSourcePackage()
563+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
564+ checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
565+ self.assertIsNot(None, checklist)
566+ self.assertTextMatchesExpressionIgnoreWhitespace("""
567+ Translation sharing configuration is incomplete.
568+ No upstream project series has been linked. Change upstream link
569+ No source branch exists for the upstream series.
570+ Translations are not enabled on the upstream series.
571+ Automatic synchronization of translations is not enabled.""",
572+ extract_text(checklist))
573+
574+ def test_checklist_partly_configured(self):
575+ # Linking a source package takes care of one item.
576+ packaging = self.factory.makePackagingLink(in_ubuntu=True)
577+ browser = self._getSharingDetailsViewBrowser(packaging.sourcepackage)
578+ checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
579+ self.assertIsNot(None, checklist)
580+ self.assertTextMatchesExpressionIgnoreWhitespace("""
581+ Translation sharing configuration is incomplete.
582+ Linked upstream series is .+ trunk series.
583+ Change upstream link Remove upstream link
584+ No source branch exists for the upstream series.
585+ Translations are not enabled on the upstream series.
586+ Automatic synchronization of translations is not enabled.""",
587+ extract_text(checklist))
588+
589+ def test_checklist_fully_configured(self):
590+ # A fully configured sharing setup.
591+ sourcepackage = self._makeFullyConfiguredSharing()[0]
592+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
593+ checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
594+ self.assertIsNot(None, checklist)
595+ self.assertTextMatchesExpressionIgnoreWhitespace("""
596+ Translation sharing with upstream is active.
597+ Linked upstream series is .+ trunk series.
598+ Change upstream link Remove upstream link
599+ Upstream source branch is .+[.]
600+ Translations are enabled on the upstream project.
601+ Automatic synchronization of translations is enabled.""",
602+ extract_text(checklist))
603+
604+ def test_potlist_only_ubuntu(self):
605+ # Without a packaging link, only Ubuntu templates are listed.
606+ sourcepackage = self._makeSourcePackage()
607+ self.factory.makePOTemplate(
608+ name='foo-template', sourcepackage=sourcepackage)
609+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
610+ tbody = find_tag_by_id(
611+ browser.contents, 'template-table').find('tbody')
612+ self.assertIsNot(None, tbody)
613+ self.assertTextMatchesExpressionIgnoreWhitespace("""
614+ foo-template only in Ubuntu 0 \d+ second(s)? ago""",
615+ extract_text(tbody))
616+
617+ def test_potlist_sharing(self):
618+ # With sharing configured, templates on both sides are listed.
619+ sourcepackage, productseries = self._makeFullyConfiguredSharing()
620+ template_name = 'foo-template'
621+ self.factory.makePOTemplate(
622+ name=template_name, sourcepackage=sourcepackage)
623+ self.factory.makePOTemplate(
624+ name=template_name, productseries=productseries)
625+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
626+ tbody = find_tag_by_id(
627+ browser.contents, 'template-table').find('tbody')
628+ self.assertIsNot(None, tbody)
629+ self.assertTextMatchesExpressionIgnoreWhitespace("""
630+ foo-template shared
631+ 0 \d+ second(s)? ago 0 \d+ second(s)? ago""",
632+ extract_text(tbody))
633+
634+ def test_potlist_only_upstream(self):
635+ # A template that is only present in upstream is called
636+ # "only in upstream".
637+ sourcepackage, productseries = self._makeFullyConfiguredSharing()
638+ template_name = 'foo-template'
639+ self.factory.makePOTemplate(
640+ name=template_name, productseries=productseries)
641+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
642+ tbody = find_tag_by_id(
643+ browser.contents, 'template-table').find('tbody')
644+ self.assertIsNot(None, tbody)
645+ self.assertTextMatchesExpressionIgnoreWhitespace("""
646+ foo-template only in upstream 0 \d+ second(s)? ago""",
647+ extract_text(tbody))
648+
649+ def test_message_no_templates(self):
650+ # When sharing is fully configured but no upstream templates are
651+ # found, a message is displayed.
652+ sourcepackage = self._makeFullyConfiguredSharing()[0]
653+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
654+ self.assertEqual(
655+ ["No upstream templates have been found yet. Please follow "
656+ "the import process by going to the Translation Import Queue "
657+ "of the upstream project series."],
658+ get_feedback_messages(browser.contents))
659+
660+ def test_no_message_with_templates(self):
661+ # When sharing is fully configured and templates are found, no
662+ # message should be displayed.
663+ sourcepackage, productseries = self._makeFullyConfiguredSharing()
664+ self.factory.makePOTemplate(productseries=productseries)
665+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
666+ self.assertEqual([], get_feedback_messages(browser.contents))
667+
668+ def test_no_message_with_incomplate_sharing(self):
669+ # When sharing is not fully configured and templates are found, no
670+ # message should be displayed.
671+ packaging = self.factory.makePackagingLink(in_ubuntu=True)
672+ productseries = packaging.productseries
673+ sourcepackage = packaging.sourcepackage
674+ self.factory.makePOTemplate(productseries=productseries)
675+ browser = self._getSharingDetailsViewBrowser(sourcepackage)
676+ self.assertEqual([], get_feedback_messages(browser.contents))
677
678=== added file 'lib/lp/translations/templates/sourcepackage-sharing-details.pt'
679--- lib/lp/translations/templates/sourcepackage-sharing-details.pt 1970-01-01 00:00:00 +0000
680+++ lib/lp/translations/templates/sourcepackage-sharing-details.pt 2011-03-15 19:18:37 +0000
681@@ -0,0 +1,121 @@
682+<html
683+ xmlns="http://www.w3.org/1999/xhtml"
684+ xmlns:tal="http://xml.zope.org/namespaces/tal"
685+ xmlns:metal="http://xml.zope.org/namespaces/metal"
686+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
687+ metal:use-macro="view/macro:page/main_only"
688+ i18n:domain="launchpad"
689+>
690+ <body>
691+ <div metal:fill-slot="heading">
692+ <h1>Translation sharing details</h1>
693+ </div>
694+ <div metal:fill-slot="main">
695+ <dl id="sharing-checklist">
696+ <dt><span tal:condition="not:view/is_configuration_complete">
697+ Translation sharing configuration is incomplete.
698+ </span>
699+ <span tal:condition="view/is_configuration_complete">
700+ Translation sharing with upstream is active.
701+ </span>
702+ </dt>
703+ <dd>
704+ <ul>
705+ <li class="sprite no"
706+ tal:condition="not:view/is_packaging_configured">
707+ No upstream project series has been linked.
708+ <a tal:replace="structure context/menu:overview/edit_packaging/fmt:icon" />
709+ </li>
710+ <li class="sprite yes"
711+ tal:condition="view/is_packaging_configured">
712+ Linked upstream series is
713+ <a tal:replace="structure context/productseries/fmt:link">
714+ Gimp trunk</a>.
715+ <a tal:replace="structure context/menu:overview/edit_packaging/fmt:icon" />
716+ <a tal:replace="structure context/menu:overview/remove_packaging/fmt:icon" />
717+ </li>
718+ <li tal:attributes="class view/no_item_class"
719+ tal:condition="not:view/has_upstream_branch">
720+ No source branch exists for the upstream series.
721+ <a tal:condition="view/is_packaging_configured"
722+ tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
723+ </li>
724+ <li class="sprite yes"
725+ tal:condition="view/has_upstream_branch">
726+ Upstream source branch is
727+ <a tal:replace="structure context/productseries/branch/fmt:link">
728+ lp:gimp</a>.
729+ <a tal:condition="view/is_packaging_configured"
730+ tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
731+ </li>
732+ <li tal:attributes="class view/no_item_class"
733+ tal:condition="not:view/is_upstream_translations_enabled">
734+ Translations are not enabled on the upstream series.
735+ <a tal:condition="view/is_packaging_configured"
736+ tal:replace="structure context/productseries/product/menu:translations/settings/fmt:icon" />
737+ </li>
738+ <li class="sprite yes"
739+ tal:condition="view/is_upstream_translations_enabled">
740+ Translations are enabled on the upstream project.
741+ <a tal:condition="view/is_packaging_configured"
742+ tal:replace="structure context/productseries/product/menu:translations/settings/fmt:icon" />
743+ </li>
744+ <li tal:attributes="class view/no_item_class"
745+ tal:condition="not:view/is_upstream_synchronization_enabled">
746+ Automatic synchronization of translations is not enabled.
747+ <a tal:condition="view/is_packaging_configured"
748+ tal:replace="structure context/productseries/menu:translations/settings/fmt:icon" />
749+ </li>
750+ <li class="sprite yes"
751+ tal:condition="view/is_upstream_synchronization_enabled">
752+ Automatic synchronization of translations is enabled.
753+ <a tal:condition="view/is_packaging_configured"
754+ tal:replace="structure context/productseries/menu:translations/settings/fmt:icon" />
755+ </li>
756+ </ul>
757+ </dd>
758+ </dl>
759+ <table class="listing" id="template-table">
760+ <thead>
761+ <tr>
762+ <th class="name_column" rowspan="2">Template name</th>
763+ <th class="state_column" rowspan="2">State</th>
764+ <th style="text-align: center" colspan="2">Ubuntu</th>
765+ <th style="text-align: center" colspan="3">Upstream</th>
766+ </tr>
767+ <tr>
768+ <th class="length_ubuntu_column">Length</th>
769+ <th class="updated_ubuntu_column">Updated</th>
770+ <th class="length_upstream_column">Length</th>
771+ <th class="updated_upstream_column">Updated</th>
772+ <th class="upstream_link_column">Upstream template</th>
773+ </tr>
774+ </thead>
775+ <tbody>
776+ <tr tal:repeat="info view/template_info">
777+ <td class="name_column">
778+ <a tal:content="info/name"
779+ tal:attributes="href info/package_template/fmt:url"
780+ tal:omit-tag="not:info/package_template">gimp20</a></td>
781+ <td class="state_column" tal:content="info/status">sharing</td>
782+ <td class="length_ubuntu_column">
783+ <tal:content tal:condition="info/package_template"
784+ tal:content="info/package_template/messagecount">82</tal:content></td>
785+ <td class="updated_ubuntu_column">
786+ <tal:content tal:condition="info/package_template"
787+ tal:content="info/package_template/date_last_updated/fmt:approximatedate">2 minutes ago</tal:content></td>
788+ <td class="length_upstream_column">
789+ <tal:content tal:condition="info/upstream_template"
790+ tal:content="info/upstream_template/messagecount">85</tal:content></td>
791+ <td class="updated_upstream_column">
792+ <tal:content tal:condition="info/upstream_template"
793+ tal:content="info/upstream_template/date_last_updated/fmt:approximatedate">2 minutes ago</tal:content></td>
794+ <td class="upstream_link_column">
795+ <a tal:condition="info/upstream_template"
796+ tal:attributes="href info/upstream_template/fmt:url">View upstream</a></td>
797+ </tr>
798+ </tbody>
799+ </table>
800+ </div>
801+ </body>
802+</html>