Merge ~twom/launchpad:oci-policy-list-some-recipes-for-tasty-ui into launchpad:master

Proposed by Tom Wardill
Status: Merged
Approved by: Tom Wardill
Approved revision: 63d589228474910dbb050037612367782e52dcf8
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~twom/launchpad:oci-policy-list-some-recipes-for-tasty-ui
Merge into: launchpad:master
Diff against target: 252 lines (+141/-13)
5 files modified
lib/lp/registry/browser/ociproject.py (+20/-2)
lib/lp/registry/browser/tests/test_ociproject.py (+63/-2)
lib/lp/registry/interfaces/ociproject.py (+3/-0)
lib/lp/registry/model/ociproject.py (+5/-0)
lib/lp/registry/templates/ociproject-index.pt (+50/-9)
Reviewer Review Type Date Requested Status
Thiago F. Pappacena (community) Approve
Colin Watson (community) Approve
Review via email: mp+396927@code.launchpad.net

Commit message

Add recipe view to OCI Project +index

Description of the change

If a project has recipes, we should use the spare space on the OCI Project page to view them.

* List official recipes, put the others behind a 'View all recipes' link.
* Move Create OCI Recipe button to right hand side context menu

https://people.canonical.com/~tomwardill/recipe-view-on-project.png

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

A few nits, but looks like a nice improvement.

review: Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Job well done! The OCIProject page is way more useful this way!

review: Approve
5f824b2... by Tom Wardill

Optimise for recipe counting

Revision history for this message
Colin Watson (cjwatson) :
63d5892... by Tom Wardill

Formatting and wording changes

Revision history for this message
Tom Wardill (twom) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
2index 380b94a..7c3f719 100644
3--- a/lib/lp/registry/browser/ociproject.py
4+++ b/lib/lp/registry/browser/ociproject.py
5@@ -187,12 +187,22 @@ class OCIProjectNavigationMenu(NavigationMenu):
6
7 facet = 'overview'
8
9- links = ('edit',)
10+ links = ('edit', 'create_recipe', 'view_recipes')
11
12 @enabled_with_permission('launchpad.Edit')
13 def edit(self):
14 return Link('+edit', 'Edit OCI project', icon='edit')
15
16+ @enabled_with_permission('launchpad.AnyLegitimatePerson')
17+ def create_recipe(self):
18+ return Link('+new-recipe', 'Create OCI recipe', icon='add')
19+
20+ def view_recipes(self):
21+ enabled = not getUtility(IOCIRecipeSet).findByOCIProject(
22+ self.context).is_empty()
23+ return Link(
24+ '+recipes', 'View all recipes', icon='info', enabled=enabled)
25+
26
27 class OCIProjectContextMenu(ContextMenu):
28 """Context menu for OCI projects."""
29@@ -211,7 +221,7 @@ class OCIProjectContextMenu(ContextMenu):
30 enabled = not getUtility(IOCIRecipeSet).findByOCIProject(
31 self.context).is_empty()
32 return Link(
33- '+recipes', 'View OCI recipes', icon='info', enabled=enabled)
34+ '+recipes', 'View all recipes', icon='info', enabled=enabled)
35
36
37 class OCIProjectIndexView(LaunchpadView):
38@@ -227,6 +237,14 @@ class OCIProjectIndexView(LaunchpadView):
39 def git_ssh_hostname(self):
40 return urlsplit(config.codehosting.git_ssh_root).hostname
41
42+ @cachedproperty
43+ def official_recipe_count(self):
44+ return self.context.getOfficialRecipes().count()
45+
46+ @cachedproperty
47+ def other_recipe_count(self):
48+ return self.context.getUnofficialRecipes().count()
49+
50
51 class OCIProjectEditView(LaunchpadEditFormView):
52 """Edit an OCI project."""
53diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
54index a8b4113..a63aa78 100644
55--- a/lib/lp/registry/browser/tests/test_ociproject.py
56+++ b/lib/lp/registry/browser/tests/test_ociproject.py
57@@ -14,7 +14,7 @@ from datetime import datetime
58 import pytz
59 from zope.security.proxy import removeSecurityProxy
60
61-from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE
62+from lp.oci.tests.helpers import OCIConfigHelperMixin
63 from lp.registry.interfaces.ociproject import (
64 OCI_PROJECT_ALLOW_CREATE,
65 OCIProjectCreateFeatureDisabled,
66@@ -79,10 +79,14 @@ class TestOCIProjectNavigation(TestCaseWithFactory):
67 self.assertEqual(oci_project, obj)
68
69
70-class TestOCIProjectView(BrowserTestCase):
71+class TestOCIProjectView(OCIConfigHelperMixin, BrowserTestCase):
72
73 layer = DatabaseFunctionalLayer
74
75+ def setUp(self):
76+ super(TestOCIProjectView, self).setUp()
77+ self.setConfig()
78+
79 def test_index_distribution_pillar(self):
80 distribution = self.factory.makeDistribution(displayname="My Distro")
81 oci_project = self.factory.makeOCIProject(
82@@ -149,6 +153,63 @@ class TestOCIProjectView(BrowserTestCase):
83 Name: oci-name
84 """, self.getMainText(oci_project, user=owner))
85
86+ def test_shows_official_recipes(self):
87+ distribution = self.factory.makeDistribution(displayname="My Distro")
88+ oci_project = self.factory.makeOCIProject(
89+ pillar=distribution, ociprojectname="oci-name")
90+ self.factory.makeOCIRecipe(oci_project=oci_project, official=True)
91+ browser = self.getViewBrowser(
92+ oci_project, view_name="+index", user=distribution.owner)
93+ self.assertIn("Official recipes", browser.contents)
94+ self.assertNotIn("unofficial recipe", browser.contents)
95+ self.assertNotIn(
96+ "There are no recipes registered for this OCI project.",
97+ browser.contents)
98+
99+ def test_shows_official_and_unofficial_recipes(self):
100+ distribution = self.factory.makeDistribution(displayname="My Distro")
101+ oci_project = self.factory.makeOCIProject(
102+ pillar=distribution, ociprojectname="oci-name")
103+ self.factory.makeOCIRecipe(oci_project=oci_project, official=True)
104+ self.factory.makeOCIRecipe(oci_project=oci_project, official=False)
105+ browser = self.getViewBrowser(
106+ oci_project, view_name="+index", user=distribution.owner)
107+ self.assertIn("Official recipes", browser.contents)
108+ self.assertIn(
109+ "There is <strong>1</strong> unofficial recipe.",
110+ browser.contents)
111+ self.assertNotIn(
112+ "There are no recipes registered for this OCI project.",
113+ browser.contents)
114+
115+ def test_shows_unofficial_recipes(self):
116+ distribution = self.factory.makeDistribution(displayname="My Distro")
117+ oci_project = self.factory.makeOCIProject(
118+ pillar=distribution, ociprojectname="oci-name")
119+ self.factory.makeOCIRecipe(oci_project=oci_project, official=False)
120+ self.factory.makeOCIRecipe(oci_project=oci_project, official=False)
121+ browser = self.getViewBrowser(
122+ oci_project, view_name="+index", user=distribution.owner)
123+ self.assertNotIn("Official recipes", browser.contents)
124+ self.assertIn(
125+ "There are <strong>2</strong> unofficial recipes.",
126+ browser.contents)
127+ self.assertNotIn(
128+ "There are no recipes registered for this OCI project.",
129+ browser.contents)
130+
131+ def test_shows_no_recipes(self):
132+ distribution = self.factory.makeDistribution(displayname="My Distro")
133+ oci_project = self.factory.makeOCIProject(
134+ pillar=distribution, ociprojectname="oci-name")
135+ browser = self.getViewBrowser(
136+ oci_project, view_name="+index", user=distribution.owner)
137+ self.assertNotIn("Official recipes", browser.contents)
138+ self.assertNotIn("unofficial recipe", browser.contents)
139+ self.assertIn(
140+ "There are no recipes registered for this OCI project.",
141+ browser.contents)
142+
143
144 class TestOCIProjectEditView(BrowserTestCase):
145
146diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
147index 718b9d7..b1d456b 100644
148--- a/lib/lp/registry/interfaces/ociproject.py
149+++ b/lib/lp/registry/interfaces/ociproject.py
150@@ -98,6 +98,9 @@ class IOCIProjectView(IHasGitRepositories, Interface):
151 def getOfficialRecipes():
152 """Gets the official recipes for this OCI project."""
153
154+ def getUnofficialRecipes():
155+ """Gets the unofficial recipes for this OCI project."""
156+
157 def getDefaultGitRepository(person):
158 """Returns the default git repository for the given user under the
159 namespace of this OCI project"""
160diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
161index 46ea7da..eabde9b 100644
162--- a/lib/lp/registry/model/ociproject.py
163+++ b/lib/lp/registry/model/ociproject.py
164@@ -205,6 +205,11 @@ class OCIProject(BugTargetBase, StormBase):
165 from lp.oci.model.ocirecipe import OCIRecipe
166 return self.getRecipes().find(OCIRecipe._official == True)
167
168+ def getUnofficialRecipes(self):
169+ """See `IOCIProject`."""
170+ from lp.oci.model.ocirecipe import OCIRecipe
171+ return self.getRecipes().find(OCIRecipe._official == False)
172+
173 def setOfficialRecipeStatus(self, recipe, status):
174 """See `IOCIProject`."""
175 if recipe is not None and recipe.oci_project != self:
176diff --git a/lib/lp/registry/templates/ociproject-index.pt b/lib/lp/registry/templates/ociproject-index.pt
177index 9fc5197..1f14e8e 100644
178--- a/lib/lp/registry/templates/ociproject-index.pt
179+++ b/lib/lp/registry/templates/ociproject-index.pt
180@@ -18,7 +18,7 @@
181 </metal:registering>
182
183 <metal:side fill-slot="side">
184- <div tal:replace="structure context/@@+global-actions"/>
185+ <tal:menu replace="structure context/@@+global-actions" />
186 </metal:side>
187
188 <metal:heading fill-slot="heading">
189@@ -65,14 +65,55 @@
190 </div>
191
192 <h2>Recipes</h2>
193- <div id="recipe-summary"
194- tal:define="link context/menu:context/view_recipes"
195- tal:condition="link/enabled"
196- tal:content="structure link/render"/>
197- <tal:create-recipe
198- define="link context/menu:context/create_recipe"
199- condition="link/enabled"
200- replace="structure link/render"/>
201+
202+ <h3 tal:condition="view/official_recipe_count">Official recipes</h3>
203+ <table class="listing" id="mirrors_list" tal:condition="view/official_recipe_count">
204+ <tbody>
205+ <tr class="head">
206+ <th>Name</th>
207+ <th>Owner</th>
208+ <th>Source</th>
209+ <th>Build file</th>
210+ <th>Date created</th>
211+ </tr>
212+
213+ <tr tal:repeat="recipe context/getOfficialRecipes">
214+ <td>
215+ <a tal:content="recipe/name"
216+ tal:attributes="href recipe/fmt:url" />
217+ </td>
218+ <td tal:content="structure recipe/owner/fmt:link" />
219+ <td>
220+ <a tal:replace="structure recipe/git_ref/fmt:link"/>
221+ </td>
222+ <td tal:content="recipe/build_file" />
223+ <td tal:content="recipe/date_created/fmt:displaydate" />
224+ </tr>
225+ </tbody>
226+ </table>
227+ <div tal:condition="python: not view.official_recipe_count and view.other_recipe_count">
228+ <p>There are no official recipes for this OCI project.</p>
229+ </div>
230+
231+ <div tal:define="count view/other_recipe_count"
232+ tal:condition="count">
233+ <span tal:condition="python: count == 1">
234+ There is <strong>1</strong> unofficial recipe.</span>
235+ <span tal:condition="python: count != 1">
236+ There are <strong tal:content="count" /> unofficial recipes.
237+ </span>
238+ <p>
239+ <tal:summary
240+ define="link context/menu:context/view_recipes"
241+ condition="link/enabled"
242+ content="structure link/render"/>
243+ </p>
244+ </div>
245+
246+ <div tal:condition="python: not view.official_recipe_count and not view.other_recipe_count">
247+ <p>There are no recipes registered for this OCI project.</p>
248+ </div>
249+
250 </div>
251 </body>
252 </html>

Subscribers

People subscribed via source and target branches

to status/vote changes: