Merge ~pappacena/launchpad:ocirecipe-sharing-lists into launchpad:master
- Git
- lp:~pappacena/launchpad
- ocirecipe-sharing-lists
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged |
---|---|
Approved by: | Thiago F. Pappacena |
Approved revision: | b4a3aecaec628183a2cd92c066894997bd3168be |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:ocirecipe-sharing-lists |
Merge into: | launchpad:master |
Prerequisite: | ~pappacena/launchpad:ocirecipe-edit-info-type-ui |
Diff against target: |
921 lines (+532/-39) 8 files modified
lib/lp/registry/browser/pillar.py (+27/-0) lib/lp/registry/browser/tests/test_pillar_sharing.py (+56/-12) lib/lp/registry/javascript/sharing/sharingdetails.js (+140/-7) lib/lp/registry/javascript/sharing/sharingdetailsview.js (+45/-1) lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js (+27/-2) lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js (+210/-1) lib/lp/registry/services/sharingservice.py (+1/-1) lib/lp/registry/templates/pillar-sharing-details.pt (+26/-15) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+400059@code.launchpad.net |
Commit message
Showing snaps and ocirecipes as shareable artifacts on +sharing pages
Description of the change
To post a comment you must log in.
- e8dcad4... by Thiago F. Pappacena
-
Merge branch 'ocirecipe-
edit-info- type-ui' into ocirecipe- sharing- lists - 8245248... by Thiago F. Pappacena
-
Merge branch 'ocirecipe-
edit-info- type-ui' into ocirecipe- sharing- lists
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
- 75fc124... by Thiago F. Pappacena
-
Removing icon and standardizing names
- 8eedbb4... by Thiago F. Pappacena
-
Merge branch 'master' into ocirecipe-
sharing- lists
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
- 3e3d9aa... by Thiago F. Pappacena
-
Fixing broken test
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
- b4a3aec... by Thiago F. Pappacena
-
Fixing texts and bug on team members count
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Pushed text and style fixes, and a pre-existing bug on team members count.
Revision history for this message
Colin Watson (cjwatson) wrote : | # |
Much better, thanks!
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/registry/browser/pillar.py b/lib/lp/registry/browser/pillar.py |
2 | index c5bfd3f..739b56f 100644 |
3 | --- a/lib/lp/registry/browser/pillar.py |
4 | +++ b/lib/lp/registry/browser/pillar.py |
5 | @@ -422,6 +422,9 @@ class PillarPersonSharingView(LaunchpadView): |
6 | bug_data = self._build_bug_template_data(self.bugtasks, request) |
7 | spec_data = self._build_specification_template_data( |
8 | self.specifications, request) |
9 | + snap_data = self._build_ocirecipe_template_data(self.snaps, request) |
10 | + ocirecipe_data = self._build_ocirecipe_template_data( |
11 | + self.ocirecipes, request) |
12 | grantee_data = { |
13 | 'displayname': self.person.displayname, |
14 | 'self_link': absoluteURL(self.person, request) |
15 | @@ -435,6 +438,8 @@ class PillarPersonSharingView(LaunchpadView): |
16 | cache.objects['branches'] = branch_data |
17 | cache.objects['gitrepositories'] = gitrepository_data |
18 | cache.objects['specifications'] = spec_data |
19 | + cache.objects['snaps'] = snap_data |
20 | + cache.objects['ocirecipes'] = ocirecipe_data |
21 | |
22 | def _loadSharedArtifacts(self): |
23 | # As a concrete can by linked via more than one policy, we use sets to |
24 | @@ -504,3 +509,25 @@ class PillarPersonSharingView(LaunchpadView): |
25 | bug_importance=importance, |
26 | information_type=information_type)) |
27 | return bug_data |
28 | + |
29 | + def _build_ocirecipe_template_data(self, oci_recipes, request): |
30 | + recipe_data = [] |
31 | + for recipe in oci_recipes: |
32 | + recipe_data.append(dict( |
33 | + self_link=absoluteURL(recipe, request), |
34 | + web_link=canonical_url(recipe, path_only_if_possible=True), |
35 | + name=recipe.name, |
36 | + id=recipe.id, |
37 | + information_type=recipe.information_type.title)) |
38 | + return recipe_data |
39 | + |
40 | + def _build_snap_template_data(self, snaps, request): |
41 | + snap_data = [] |
42 | + for snap in snaps: |
43 | + snap_data.append(dict( |
44 | + self_link=absoluteURL(snap, request), |
45 | + web_link=canonical_url(snap, path_only_if_possible=True), |
46 | + name=snap.name, |
47 | + id=snap.id, |
48 | + information_type=snap.information_type.title)) |
49 | + return snap_data |
50 | diff --git a/lib/lp/registry/browser/tests/test_pillar_sharing.py b/lib/lp/registry/browser/tests/test_pillar_sharing.py |
51 | index 432c473..082276d 100644 |
52 | --- a/lib/lp/registry/browser/tests/test_pillar_sharing.py |
53 | +++ b/lib/lp/registry/browser/tests/test_pillar_sharing.py |
54 | @@ -25,6 +25,7 @@ from lp.registry.enums import ( |
55 | BranchSharingPolicy, |
56 | BugSharingPolicy, |
57 | PersonVisibility, |
58 | + TeamMembershipPolicy, |
59 | ) |
60 | from lp.registry.interfaces.accesspolicy import IAccessPolicyGrantFlatSource |
61 | from lp.registry.model.pillar import PillarPerson |
62 | @@ -179,8 +180,9 @@ class PillarSharingDetailsMixin: |
63 | pillarperson.pillar.name, pillarperson.person.name) |
64 | browser = self.getUserBrowser(user=self.owner, url=url) |
65 | self.assertIn( |
66 | - 'There are no shared bugs, Bazaar branches, Git repositories, or ' |
67 | - 'blueprints.', normalize_whitespace(browser.contents)) |
68 | + 'There are no shared bugs, Bazaar branches, Git repositories, ' |
69 | + 'snap recipes, OCI recipes or blueprints.', |
70 | + normalize_whitespace(browser.contents)) |
71 | |
72 | def test_init_works(self): |
73 | # The view works with a feature flag. |
74 | @@ -386,23 +388,69 @@ class PillarSharingViewTestMixin: |
75 | team_name, |
76 | [grantee['name'] for grantee in cache.objects['grantee_data']]) |
77 | |
78 | - def test_pillar_person_sharing(self): |
79 | + def test_pillar_person_sharing_with_team(self): |
80 | self.useFixture(FeatureFixture({ |
81 | SNAP_PRIVATE_FEATURE_FLAG: 'on', |
82 | OCI_RECIPE_ALLOW_CREATE: 'on'})) |
83 | - totals = {"oci_recipes": 1, "snaps": 0} |
84 | + team = self.factory.makeTeam( |
85 | + membership_policy=TeamMembershipPolicy.MODERATED) |
86 | + # Add 4 members to the team, so we should have the team owner + 4 |
87 | + # other members with access to the artifacts. |
88 | + for i in range(4): |
89 | + self.factory.makePerson(member_of=[team]) |
90 | + |
91 | items = [ |
92 | self.factory.makeOCIRecipe( |
93 | owner=self.owner, registrant=self.owner, |
94 | information_type=InformationType.USERDATA, |
95 | oci_project=self.factory.makeOCIProject(pillar=self.pillar))] |
96 | + expected_text = """ |
97 | + 5 team members can view these artifacts. |
98 | + Shared with %s: |
99 | + 1 OCI recipes |
100 | + """ % team.displayname |
101 | + |
102 | if self.pillar_type == 'product': |
103 | - totals["snaps"] = 1 |
104 | items.append(self.factory.makeSnap( |
105 | information_type=InformationType.USERDATA, |
106 | owner=self.owner, registrant=self.owner, project=self.pillar)) |
107 | + expected_text += "\n1 snap recipes" |
108 | + |
109 | + with person_logged_in(self.owner): |
110 | + for item in items: |
111 | + item.subscribe(team, self.owner) |
112 | |
113 | + pillarperson = PillarPerson(self.pillar, team) |
114 | + url = 'http://launchpad.test/%s/+sharing/%s' % ( |
115 | + pillarperson.pillar.name, pillarperson.person.name) |
116 | + browser = self.getUserBrowser(user=self.owner, url=url) |
117 | + content = extract_text( |
118 | + find_tag_by_id(browser.contents, "observer-summary")) |
119 | + |
120 | + self.assertTextMatchesExpressionIgnoreWhitespace( |
121 | + expected_text, content) |
122 | + |
123 | + def test_pillar_person_sharing(self): |
124 | + self.useFixture(FeatureFixture({ |
125 | + SNAP_PRIVATE_FEATURE_FLAG: 'on', |
126 | + OCI_RECIPE_ALLOW_CREATE: 'on'})) |
127 | person = self.factory.makePerson() |
128 | + items = [ |
129 | + self.factory.makeOCIRecipe( |
130 | + owner=self.owner, registrant=self.owner, |
131 | + information_type=InformationType.USERDATA, |
132 | + oci_project=self.factory.makeOCIProject(pillar=self.pillar))] |
133 | + expected_text = """ |
134 | + Shared with %s: |
135 | + 1 OCI recipes |
136 | + """ % person.displayname |
137 | + |
138 | + if self.pillar_type == 'product': |
139 | + items.append(self.factory.makeSnap( |
140 | + information_type=InformationType.USERDATA, |
141 | + owner=self.owner, registrant=self.owner, project=self.pillar)) |
142 | + expected_text += "\n1 snap recipes" |
143 | + |
144 | with person_logged_in(self.owner): |
145 | for item in items: |
146 | item.subscribe(person, self.owner) |
147 | @@ -413,13 +461,9 @@ class PillarSharingViewTestMixin: |
148 | browser = self.getUserBrowser(user=self.owner, url=url) |
149 | content = extract_text( |
150 | find_tag_by_id(browser.contents, "observer-summary")) |
151 | - self.assertTextMatchesExpressionIgnoreWhitespace(""" |
152 | - 0 bugs, |
153 | - 0 Bazaar branches, |
154 | - 0 Git repositories, |
155 | - %(snaps)s snaps, |
156 | - and 0 blueprints shared |
157 | - """ % totals, content) |
158 | + |
159 | + self.assertTextMatchesExpressionIgnoreWhitespace( |
160 | + expected_text, content) |
161 | |
162 | |
163 | class TestProductSharingView(PillarSharingViewTestMixin, |
164 | diff --git a/lib/lp/registry/javascript/sharing/sharingdetails.js b/lib/lp/registry/javascript/sharing/sharingdetails.js |
165 | index 0ac4f32..9c4f002 100644 |
166 | --- a/lib/lp/registry/javascript/sharing/sharingdetails.js |
167 | +++ b/lib/lp/registry/javascript/sharing/sharingdetails.js |
168 | @@ -1,4 +1,4 @@ |
169 | -/* Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
170 | +/* Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
171 | * GNU Affero General Public License version 3 (see the file LICENSE). |
172 | * |
173 | * Sharing details widget |
174 | @@ -148,6 +148,58 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
175 | ].join(' '); |
176 | }, |
177 | |
178 | + _snap_details_row_template: function() { |
179 | + return [ |
180 | + '<tr id="shared-snap-{{id}}">', |
181 | + ' <td>', |
182 | + ' <span class="sortkey">{{id}}</span>', |
183 | + ' <a href="{{web_link}}">', |
184 | + ' {{name}}', |
185 | + ' </a>', |
186 | + ' </td>', |
187 | + ' <td class="action-icons nowrap">', |
188 | + ' <span id="remove-snap-{{id}}">', |
189 | + ' <a class="sprite remove action-icon" href="#"', |
190 | + ' title="Unshare Snap {{name}} with {{displayname}}"', |
191 | + ' data-self_link="{{self_link}}" data-name="{{name}}"', |
192 | + ' data-type="snap">Remove</a>', |
193 | + ' </span>', |
194 | + ' </td>', |
195 | + ' <td>', |
196 | + ' <span class="information_type">', |
197 | + ' {{information_type}}', |
198 | + ' </span>', |
199 | + ' </td>', |
200 | + '</tr>' |
201 | + ].join(' '); |
202 | + }, |
203 | + |
204 | + _ocirecipe_details_row_template: function() { |
205 | + return [ |
206 | + '<tr id="shared-ocirecipe-{{id}}">', |
207 | + ' <td>', |
208 | + ' <span class="sortkey">{{id}}</span>', |
209 | + ' <a href="{{web_link}}">', |
210 | + ' {{name}}', |
211 | + ' </a>', |
212 | + ' </td>', |
213 | + ' <td class="action-icons nowrap">', |
214 | + ' <span id="remove-ocirecipe-{{id}}">', |
215 | + ' <a class="sprite remove action-icon" href="#"', |
216 | + ' title="Unshare OCI recipe {{name}} with {{displayname}}"', |
217 | + ' data-self_link="{{self_link}}" data-name="{{name}}"', |
218 | + ' data-type="ocirecipe">Remove</a>', |
219 | + ' </span>', |
220 | + ' </td>', |
221 | + ' <td>', |
222 | + ' <span class="information_type">', |
223 | + ' {{information_type}}', |
224 | + ' </span>', |
225 | + ' </td>', |
226 | + '</tr>' |
227 | + ].join(' '); |
228 | + }, |
229 | + |
230 | _table_body_template: function() { |
231 | return [ |
232 | '<tbody id="sharing-table-body">', |
233 | @@ -160,6 +212,12 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
234 | '{{#gitrepositories}}', |
235 | '{{> gitrepository}}', |
236 | '{{/gitrepositories}}', |
237 | + '{{#snaps}}', |
238 | + '{{> snap}}', |
239 | + '{{/snaps}}', |
240 | + '{{#ocirecipes}}', |
241 | + '{{> ocirecipe}}', |
242 | + '{{/ocirecipes}}', |
243 | '{{#specifications}}', |
244 | '{{> spec}}', |
245 | '{{/specifications}}', |
246 | @@ -183,7 +241,7 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
247 | |
248 | // Delete the specified grantees from the table. |
249 | delete_artifacts: function(bugs, branches, gitrepositories, specifications, |
250 | - all_rows_deleted) { |
251 | + snaps, ocirecipes, all_rows_deleted) { |
252 | var deleted_row_selectors = []; |
253 | var details_table_body = this.get('details_table_body'); |
254 | Y.Array.each(bugs, function(bug) { |
255 | @@ -215,6 +273,20 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
256 | deleted_row_selectors.push(selector); |
257 | } |
258 | }); |
259 | + Y.Array.each(snaps, function(snap) { |
260 | + var selector = 'tr[id=shared-snap-' + snap.id + ']'; |
261 | + var table_row = details_table_body.one(selector); |
262 | + if (Y.Lang.isValue(table_row)) { |
263 | + deleted_row_selectors.push(selector); |
264 | + } |
265 | + }); |
266 | + Y.Array.each(ocirecipes, function(ocirecipe) { |
267 | + var selector = 'tr[id=shared-ocirecipe-' + ocirecipe.id + ']'; |
268 | + var table_row = details_table_body.one(selector); |
269 | + if (Y.Lang.isValue(table_row)) { |
270 | + deleted_row_selectors.push(selector); |
271 | + } |
272 | + }); |
273 | |
274 | if (deleted_row_selectors.length === 0) { |
275 | return; |
276 | @@ -232,7 +304,8 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
277 | .appendChild('<td colspan="3"></td>') |
278 | .setContent( |
279 | "There are no shared bugs, Bazaar branches, " + |
280 | - "Git repositories, or blueprints."); |
281 | + "Git repositories, snap recipes, OCI recipes or " + |
282 | + "blueprints."); |
283 | } |
284 | }; |
285 | var anim_duration = this.get('anim_duration'); |
286 | @@ -275,6 +348,12 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
287 | this.set( |
288 | 'spec_details_row_template', |
289 | this._spec_details_row_template()); |
290 | + this.set( |
291 | + 'snap_details_row_template', |
292 | + this._snap_details_row_template()); |
293 | + this.set( |
294 | + 'ocirecipe_details_row_template', |
295 | + this._ocirecipe_details_row_template()); |
296 | |
297 | this.set( |
298 | 'table_body_template', |
299 | @@ -288,16 +367,21 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
300 | var gitrepositories = this.get('gitrepositories'); |
301 | var bugs = this.get('bugs'); |
302 | var specs = this.get('specifications'); |
303 | + var snaps = this.get('snaps'); |
304 | + var ocirecipes = this.get('ocirecipes'); |
305 | |
306 | if (bugs.length === 0 && branches.length === 0 && |
307 | - gitrepositories.length === 0 && specs.length === 0 ) { |
308 | + gitrepositories.length === 0 && specs.length === 0 && |
309 | + snaps.length === 0 && ocirecipes.length === 0) { |
310 | return; |
311 | } |
312 | var partials = { |
313 | branch: this.get('branch_details_row_template'), |
314 | gitrepository: this.get('gitrepository_details_row_template'), |
315 | bug: this.get('bug_details_row_template'), |
316 | - spec: this.get('spec_details_row_template') |
317 | + spec: this.get('spec_details_row_template'), |
318 | + snap: this.get('snap_details_row_template'), |
319 | + ocirecipe: this.get('ocirecipe_details_row_template') |
320 | }; |
321 | var template = this.get('table_body_template'); |
322 | var html = Y.lp.mustache.to_html( |
323 | @@ -307,6 +391,8 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
324 | gitrepositories: gitrepositories, |
325 | bugs: bugs, |
326 | specifications: specs, |
327 | + snaps: snaps, |
328 | + ocirecipes: ocirecipes, |
329 | displayname: this.get('person_name') |
330 | }, |
331 | partials); |
332 | @@ -344,14 +430,20 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
333 | var existing_branches = this.get('branches'); |
334 | var existing_gitrepositories = this.get('gitrepositories'); |
335 | var existing_specifications = this.get('specifications'); |
336 | + var existing_snaps = this.get('snaps'); |
337 | + var existing_ocirecipes = this.get('ocirecipes'); |
338 | var model_bugs = LP.cache.bugs; |
339 | var model_branches = LP.cache.branches; |
340 | var model_gitrepositories = LP.cache.gitrepositories; |
341 | var model_specifications = LP.cache.specifications; |
342 | + var model_snaps = LP.cache.snaps; |
343 | + var model_ocirecipes = LP.cache.ocirecipes; |
344 | var deleted_bugs = []; |
345 | var deleted_branches = []; |
346 | var deleted_gitrepositories = []; |
347 | var deleted_specifications = []; |
348 | + var deleted_snaps = []; |
349 | + var deleted_ocirecipes = []; |
350 | |
351 | var self = this; |
352 | Y.Array.each(existing_bugs, function(bug) { |
353 | @@ -388,12 +480,29 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
354 | } |
355 | }); |
356 | |
357 | + Y.Array.each(existing_snaps, function(snap) { |
358 | + var model_snap = self._get_artifact_from_model( |
359 | + snap.id, 'id', model_snaps); |
360 | + if (!Y.Lang.isValue(model_snap)) { |
361 | + deleted_snaps.push(snap); |
362 | + } |
363 | + }); |
364 | + |
365 | + Y.Array.each(existing_ocirecipes, function(ocirecipe) { |
366 | + var model_ocirecipe = self._get_artifact_from_model( |
367 | + ocirecipe.id, 'id', model_ocirecipes); |
368 | + if (!Y.Lang.isValue(model_ocirecipe)) { |
369 | + deleted_ocirecipes.push(ocirecipe); |
370 | + } |
371 | + }); |
372 | + |
373 | if (deleted_bugs.length > 0 || deleted_branches.length > 0 || |
374 | deleted_gitrepositories.length > 0 || |
375 | - deleted_specifications.length > 0) { |
376 | + deleted_specifications.length > 0 || deleted_snaps.length > 0 || |
377 | + deleted_ocirecipes.length > 0) { |
378 | this.delete_artifacts( |
379 | deleted_bugs, deleted_branches, deleted_gitrepositories, |
380 | - deleted_specifications, |
381 | + deleted_specifications, deleted_snaps, deleted_ocirecipes, |
382 | model_bugs.length === 0 && model_branches.length === 0 && |
383 | model_gitrepositories.length === 0 && |
384 | deleted_specifications.length === 0); |
385 | @@ -403,6 +512,8 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
386 | this.set('branches', model_branches); |
387 | this.set('gitrepositories', model_gitrepositories); |
388 | this.set('specifications', model_specifications); |
389 | + this.set('snaps', model_snaps); |
390 | + this.set('ocirecipes', model_ocirecipes); |
391 | |
392 | Y.lp.app.sorttable.SortTable.registerSortKeyFunction( |
393 | 'branchsortkey', this.branch_sort_key); |
394 | @@ -433,6 +544,14 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
395 | value: null |
396 | }, |
397 | |
398 | + snap_details_row_template: { |
399 | + value: null |
400 | + }, |
401 | + |
402 | + ocirecipe_details_row_template: { |
403 | + value: null |
404 | + }, |
405 | + |
406 | branches: { |
407 | value: [], |
408 | // We clone the data passed in so external modifications do not |
409 | @@ -454,6 +573,20 @@ ns.SharingDetailsTable = Y.Base.create('sharingDetailsTable', Y.Widget, [], { |
410 | setter: clone_data |
411 | }, |
412 | |
413 | + snaps: { |
414 | + value: [], |
415 | + // We clone the data passed in so external modifications do not |
416 | + // interfere. |
417 | + setter: clone_data |
418 | + }, |
419 | + |
420 | + ocirecipes: { |
421 | + value: [], |
422 | + // We clone the data passed in so external modifications do not |
423 | + // interfere. |
424 | + setter: clone_data |
425 | + }, |
426 | + |
427 | // The node holding the details table. |
428 | details_table_body: { |
429 | getter: function() { |
430 | diff --git a/lib/lp/registry/javascript/sharing/sharingdetailsview.js b/lib/lp/registry/javascript/sharing/sharingdetailsview.js |
431 | index b41e9fe..d4197be 100644 |
432 | --- a/lib/lp/registry/javascript/sharing/sharingdetailsview.js |
433 | +++ b/lib/lp/registry/javascript/sharing/sharingdetailsview.js |
434 | @@ -1,4 +1,4 @@ |
435 | -/* Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
436 | +/* Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
437 | * GNU Affero General Public License version 3 (see the file LICENSE). |
438 | * |
439 | * Disclosure infrastructure. |
440 | @@ -32,6 +32,8 @@ Y.extend(SharingDetailsView, Y.Widget, { |
441 | bugs: LP.cache.bugs, |
442 | branches: LP.cache.branches, |
443 | gitrepositories: LP.cache.gitrepositories, |
444 | + snaps: LP.cache.snaps, |
445 | + ocirecipes: LP.cache.ocirecipes, |
446 | person_name: LP.cache.grantee.displayname, |
447 | specifications: LP.cache.specifications, |
448 | write_enabled: true |
449 | @@ -85,6 +87,22 @@ Y.extend(SharingDetailsView, Y.Widget, { |
450 | } |
451 | }); |
452 | break; |
453 | + case 'snap': |
454 | + Y.Array.some(LP.cache.snaps, function(snap) { |
455 | + if (snap.self_link === artifact_uri) { |
456 | + artifact_id = snap.id; |
457 | + return true; |
458 | + } |
459 | + }); |
460 | + break; |
461 | + case 'ocirecipe': |
462 | + Y.Array.some(LP.cache.ocirecipes, function(ocirecipe) { |
463 | + if (ocirecipe.self_link === artifact_uri) { |
464 | + artifact_id = ocirecipe.id; |
465 | + return true; |
466 | + } |
467 | + }); |
468 | + break; |
469 | case 'spec': |
470 | Y.Array.some(LP.cache.specifications, function(spec) { |
471 | if (spec.self_link === artifact_uri) { |
472 | @@ -207,6 +225,22 @@ Y.extend(SharingDetailsView, Y.Widget, { |
473 | return true; |
474 | } |
475 | }); |
476 | + var snap_data = LP.cache.snaps; |
477 | + Y.Array.some(snap_data, function(snap, index) { |
478 | + if (snap.self_link === artifact_uri) { |
479 | + snap_data.splice(index, 1); |
480 | + self.syncUI(); |
481 | + return true; |
482 | + } |
483 | + }); |
484 | + var ocirecipe_data = LP.cache.ocirecipes; |
485 | + Y.Array.some(ocirecipe_data, function(ocirecipe, index) { |
486 | + if (ocirecipe.self_link === artifact_uri) { |
487 | + ocirecipe_data.splice(index, 1); |
488 | + self.syncUI(); |
489 | + return true; |
490 | + } |
491 | + }); |
492 | }, |
493 | |
494 | /** |
495 | @@ -223,6 +257,8 @@ Y.extend(SharingDetailsView, Y.Widget, { |
496 | var branches = []; |
497 | var gitrepositories = []; |
498 | var specifications = []; |
499 | + var snaps = []; |
500 | + var ocirecipes = []; |
501 | switch (artifact_type) { |
502 | case 'bug': |
503 | bugs = [artifact_uri]; |
504 | @@ -233,6 +269,12 @@ Y.extend(SharingDetailsView, Y.Widget, { |
505 | case 'gitrepository': |
506 | gitrepositories = [artifact_uri]; |
507 | break; |
508 | + case 'snap': |
509 | + snaps = [artifact_uri]; |
510 | + break; |
511 | + case 'ocirecipe': |
512 | + ocirecipes = [artifact_uri]; |
513 | + break; |
514 | case 'spec': |
515 | specifications = [artifact_uri]; |
516 | break; |
517 | @@ -256,6 +298,8 @@ Y.extend(SharingDetailsView, Y.Widget, { |
518 | bugs: bugs, |
519 | branches: branches, |
520 | gitrepositories: gitrepositories, |
521 | + snaps: snaps, |
522 | + ocirecipes: ocirecipes, |
523 | specifications: specifications |
524 | } |
525 | }; |
526 | diff --git a/lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js b/lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js |
527 | index 7b7d306..b78795e 100644 |
528 | --- a/lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js |
529 | +++ b/lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js |
530 | @@ -1,4 +1,4 @@ |
531 | -/* Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
532 | +/* Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
533 | * GNU Affero General Public License version 3 (see the file LICENSE). */ |
534 | YUI.add('lp.registry.sharing.sharingdetails.test', function(Y) { |
535 | |
536 | @@ -44,6 +44,26 @@ YUI.add('lp.registry.sharing.sharingdetails.test', function(Y) { |
537 | repository_name: '~someone/+git/somerepo', |
538 | information_type: 'Private' |
539 | } |
540 | + ], |
541 | + snaps: [ |
542 | + { |
543 | + self_link: 'api/devel/~someone/+snap/somesnap', |
544 | + web_link: '/~someone/+snap/somesnap', |
545 | + id: '2', |
546 | + name: 'snap-name', |
547 | + information_type: 'Private' |
548 | + } |
549 | + ], |
550 | + ocirecipes: [ |
551 | + { |
552 | + self_link: ('api/devel/~someone/proj/+oci/' + |
553 | + 'ociproj/+recipe/recipe'), |
554 | + web_link: ('~someone/proj/+oci/' + |
555 | + 'ociproj/+recipe/recipe'), |
556 | + name: 'ocirecipe-name', |
557 | + id: '2', |
558 | + information_type: 'Private' |
559 | + } |
560 | ] |
561 | } |
562 | }; |
563 | @@ -71,6 +91,8 @@ YUI.add('lp.registry.sharing.sharingdetails.test', function(Y) { |
564 | bugs: window.LP.cache.bugs, |
565 | branches: window.LP.cache.branches, |
566 | gitrepositories: window.LP.cache.gitrepositories, |
567 | + snaps: window.LP.cache.snaps, |
568 | + ocirecipes: window.LP.cache.ocirecipes, |
569 | write_enabled: true |
570 | }, overrides); |
571 | window.LP.cache.grantee_data = config.grantees; |
572 | @@ -326,10 +348,13 @@ YUI.add('lp.registry.sharing.sharingdetails.test', function(Y) { |
573 | [window.LP.cache.bugs[0]], |
574 | [window.LP.cache.branches[0]], |
575 | [window.LP.cache.gitrepositories[0]], |
576 | + [], // specs |
577 | + [window.LP.cache.snaps[0]], |
578 | + [window.LP.cache.ocirecipes[0]], |
579 | true); |
580 | Y.Assert.areEqual( |
581 | 'There are no shared bugs, Bazaar branches, Git ' + |
582 | - 'repositories, or blueprints.', |
583 | + 'repositories, snap recipes, OCI recipes or blueprints.', |
584 | Y.one('#sharing-table-body tr').get('text')); |
585 | }, |
586 | |
587 | diff --git a/lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js b/lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js |
588 | index 84f7930..bdcdcfb 100644 |
589 | --- a/lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js |
590 | +++ b/lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js |
591 | @@ -1,4 +1,4 @@ |
592 | -/* Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
593 | +/* Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
594 | * GNU Affero General Public License version 3 (see the file LICENSE). */ |
595 | |
596 | YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) { |
597 | @@ -49,6 +49,26 @@ YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) { |
598 | web_link: "/obsolete-junk/+spec/big-project" |
599 | } |
600 | ], |
601 | + snaps: [ |
602 | + { |
603 | + self_link: 'api/devel/~someone/+snap/somesnap', |
604 | + web_link: '/~someone/+snap/somesnap', |
605 | + id: '2', |
606 | + name: 'snap-name', |
607 | + information_type: 'Private' |
608 | + } |
609 | + ], |
610 | + ocirecipes: [ |
611 | + { |
612 | + self_link: ('api/devel/~someone/proj/+oci/' + |
613 | + 'ociproj/+recipe/recipe'), |
614 | + web_link: ('~someone/proj/+oci/' + |
615 | + 'ociproj/+recipe/recipe'), |
616 | + name: 'ocirecipe-name', |
617 | + id: '2', |
618 | + information_type: 'Private' |
619 | + } |
620 | + ], |
621 | grantee: { |
622 | displayname: 'Fred Bloggs', |
623 | self_link: '~fred' |
624 | @@ -185,6 +205,51 @@ YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) { |
625 | Y.Assert.isTrue(confirmRemove_called); |
626 | }, |
627 | |
628 | + // Clicking a Snap remove link calls the |
629 | + // confirm_grant_removal method with the correct parameters. |
630 | + test_remove_snap_grant_click: function() { |
631 | + this.view = this._create_Widget(); |
632 | + this.view.render(); |
633 | + var confirmRemove_called = false; |
634 | + this.view.confirm_grant_removal = function( |
635 | + delete_link, artifact_uri, artifact_name, artifact_type) { |
636 | + Y.Assert.areEqual( |
637 | + 'api/devel/~someone/+snap/somesnap', artifact_uri); |
638 | + Y.Assert.areEqual('snap-name', artifact_name); |
639 | + Y.Assert.areEqual('snap', artifact_type); |
640 | + Y.Assert.areEqual(delete_link_to_click, delete_link); |
641 | + confirmRemove_called = true; |
642 | + |
643 | + }; |
644 | + var delete_link_to_click = |
645 | + Y.one('#sharing-table-body span[id=remove-snap-2] a'); |
646 | + delete_link_to_click.simulate('click'); |
647 | + Y.Assert.isTrue(confirmRemove_called); |
648 | + }, |
649 | + |
650 | + // Clicking an OCI recipe remove link calls the |
651 | + // confirm_grant_removal method with the correct parameters. |
652 | + test_remove_ocirecipe_grant_click: function() { |
653 | + this.view = this._create_Widget(); |
654 | + this.view.render(); |
655 | + var confirmRemove_called = false; |
656 | + this.view.confirm_grant_removal = function( |
657 | + delete_link, artifact_uri, artifact_name, artifact_type) { |
658 | + Y.Assert.areEqual( |
659 | + 'api/devel/~someone/proj/+oci/ociproj/+recipe/recipe', |
660 | + artifact_uri); |
661 | + Y.Assert.areEqual('ocirecipe-name', artifact_name); |
662 | + Y.Assert.areEqual('ocirecipe', artifact_type); |
663 | + Y.Assert.areEqual(delete_link_to_click, delete_link); |
664 | + confirmRemove_called = true; |
665 | + |
666 | + }; |
667 | + var delete_link_to_click = |
668 | + Y.one('#sharing-table-body span[id=remove-ocirecipe-2] a'); |
669 | + delete_link_to_click.simulate('click'); |
670 | + Y.Assert.isTrue(confirmRemove_called); |
671 | + }, |
672 | + |
673 | //Test the behaviour of the removal confirmation dialog. |
674 | _test_confirm_grant_removal: function(click_ok) { |
675 | this.view = this._create_Widget(); |
676 | @@ -342,6 +407,88 @@ YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) { |
677 | Y.Assert.isTrue(remove_grant_success_called); |
678 | }, |
679 | |
680 | + // The perform_remove_grant method makes the expected XHR calls when a |
681 | + // Snap grant remove link is clicked. |
682 | + test_perform_remove_snap_grant: function() { |
683 | + var mockio = new Y.lp.testing.mockio.MockIo(); |
684 | + var lp_client = new Y.lp.client.Launchpad({ |
685 | + io_provider: mockio |
686 | + }); |
687 | + this.view = this._create_Widget({ |
688 | + lp_client: lp_client |
689 | + }); |
690 | + this.view.render(); |
691 | + var remove_grant_success_called = false; |
692 | + this.view.remove_grant_success = function(artifact_uri) { |
693 | + Y.Assert.areEqual( |
694 | + 'api/devel/~someone/+snap/somesnap', artifact_uri); |
695 | + remove_grant_success_called = true; |
696 | + }; |
697 | + var delete_link = |
698 | + Y.one('#sharing-table-body span[id=remove-snap-2] a'); |
699 | + this.view.perform_remove_grant( |
700 | + delete_link, 'api/devel/~someone/+snap/somesnap', |
701 | + 'snap'); |
702 | + Y.Assert.areEqual( |
703 | + '/api/devel/+services/sharing', |
704 | + mockio.last_request.url); |
705 | + var expected_qs = ''; |
706 | + expected_qs = Y.lp.client.append_qs( |
707 | + expected_qs, 'ws.op', 'revokeAccessGrants'); |
708 | + expected_qs = Y.lp.client.append_qs( |
709 | + expected_qs, 'pillar', '/pillar'); |
710 | + expected_qs = Y.lp.client.append_qs( |
711 | + expected_qs, 'grantee', '~fred'); |
712 | + expected_qs = Y.lp.client.append_qs( |
713 | + expected_qs, 'snaps', |
714 | + 'api/devel/~someone/+snap/somesnap'); |
715 | + Y.Assert.areEqual(expected_qs, mockio.last_request.config.data); |
716 | + mockio.last_request.successJSON({}); |
717 | + Y.Assert.isTrue(remove_grant_success_called); |
718 | + }, |
719 | + |
720 | + // The perform_remove_grant method makes the expected XHR calls when an |
721 | + // OCI recipe grant remove link is clicked. |
722 | + test_perform_remove_ocirecipe_grant: function() { |
723 | + var mockio = new Y.lp.testing.mockio.MockIo(); |
724 | + var lp_client = new Y.lp.client.Launchpad({ |
725 | + io_provider: mockio |
726 | + }); |
727 | + this.view = this._create_Widget({ |
728 | + lp_client: lp_client |
729 | + }); |
730 | + this.view.render(); |
731 | + var remove_grant_success_called = false; |
732 | + this.view.remove_grant_success = function(artifact_uri) { |
733 | + Y.Assert.areEqual( |
734 | + 'api/devel/~someone/proj/+oci/ociproj/+recipe/recipe', |
735 | + artifact_uri); |
736 | + remove_grant_success_called = true; |
737 | + }; |
738 | + var delete_link = |
739 | + Y.one('#sharing-table-body span[id=remove-ocirecipe-2] a'); |
740 | + this.view.perform_remove_grant( |
741 | + delete_link, |
742 | + 'api/devel/~someone/proj/+oci/ociproj/+recipe/recipe', |
743 | + 'ocirecipe'); |
744 | + Y.Assert.areEqual( |
745 | + '/api/devel/+services/sharing', |
746 | + mockio.last_request.url); |
747 | + var expected_qs = ''; |
748 | + expected_qs = Y.lp.client.append_qs( |
749 | + expected_qs, 'ws.op', 'revokeAccessGrants'); |
750 | + expected_qs = Y.lp.client.append_qs( |
751 | + expected_qs, 'pillar', '/pillar'); |
752 | + expected_qs = Y.lp.client.append_qs( |
753 | + expected_qs, 'grantee', '~fred'); |
754 | + expected_qs = Y.lp.client.append_qs( |
755 | + expected_qs, 'ocirecipes', |
756 | + 'api/devel/~someone/proj/+oci/ociproj/+recipe/recipe'); |
757 | + Y.Assert.areEqual(expected_qs, mockio.last_request.config.data); |
758 | + mockio.last_request.successJSON({}); |
759 | + Y.Assert.isTrue(remove_grant_success_called); |
760 | + }, |
761 | + |
762 | // The remove bug grant callback updates the model and syncs the UI. |
763 | test_remove_bug_grant_success: function() { |
764 | this.view = this._create_Widget({anim_duration: 0}); |
765 | @@ -410,6 +557,40 @@ YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) { |
766 | 'All specs are removed from the cache.'); |
767 | }, |
768 | |
769 | + // The remove Snap grant callback updates the model and |
770 | + // syncs the UI. |
771 | + test_remove_snap_grant_success: function() { |
772 | + this.view = this._create_Widget({anim_duration: 0}); |
773 | + this.view.render(); |
774 | + var syncUI_called = false; |
775 | + this.view.syncUI = function() { |
776 | + syncUI_called = true; |
777 | + }; |
778 | + this.view.remove_grant_success( |
779 | + 'api/devel/~someone/+snap/somesnap'); |
780 | + Y.Assert.isTrue(syncUI_called); |
781 | + Y.Array.each(window.LP.cache.snaps, function(snap) { |
782 | + Y.Assert.areNotEqual(2, snap.id); |
783 | + }); |
784 | + }, |
785 | + |
786 | + // The remove OCI recipe grant callback updates the model and |
787 | + // syncs the UI. |
788 | + test_remove_ocirecipe_grant_success: function() { |
789 | + this.view = this._create_Widget({anim_duration: 0}); |
790 | + this.view.render(); |
791 | + var syncUI_called = false; |
792 | + this.view.syncUI = function() { |
793 | + syncUI_called = true; |
794 | + }; |
795 | + this.view.remove_grant_success( |
796 | + 'api/devel/~someone/proj/+oci/ociproj/+recipe/recipe'); |
797 | + Y.Assert.isTrue(syncUI_called); |
798 | + Y.Array.each(window.LP.cache.ocirecipes, function(recipe) { |
799 | + Y.Assert.areNotEqual(2, recipe.id); |
800 | + }); |
801 | + }, |
802 | + |
803 | // XHR calls display errors correctly. |
804 | _assert_error_displayed_on_failure: function( |
805 | artifact, invoke_operation) { |
806 | @@ -491,6 +672,34 @@ YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) { |
807 | }; |
808 | this._assert_error_displayed_on_failure('spec', invoke_remove); |
809 | }, |
810 | + |
811 | + // The perform_remove_grant method handles errors correctly with Snaps. |
812 | + test_perform_remove_snap_error: function() { |
813 | + var invoke_remove = function(view) { |
814 | + var delete_link = |
815 | + Y.one('#sharing-table-body span[id=remove-snap-2] a'); |
816 | + view.perform_remove_grant( |
817 | + delete_link, 'api/devel/~someone/+snap/somesnap', |
818 | + 'snap'); |
819 | + }; |
820 | + this._assert_error_displayed_on_failure('snap', invoke_remove); |
821 | + }, |
822 | + |
823 | + // The perform_remove_grant method handles errors correctly with OCI |
824 | + // recipes. |
825 | + test_perform_remove_ocirecipe_error: function() { |
826 | + var invoke_remove = function(view) { |
827 | + var delete_link = |
828 | + Y.one('#sharing-table-body span[id=remove-ocirecipe-2] a'); |
829 | + view.perform_remove_grant( |
830 | + delete_link, |
831 | + 'api/devel/~someone/proj/+oci/ociproj/+recipe/recipe', |
832 | + 'ocirecipe'); |
833 | + }; |
834 | + this._assert_error_displayed_on_failure( |
835 | + 'ocirecipe', invoke_remove); |
836 | + }, |
837 | + |
838 | // Test that syncUI works as expected. |
839 | test_syncUI: function() { |
840 | this.view = this._create_Widget(); |
841 | diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py |
842 | index eb4b135..2e59510 100644 |
843 | --- a/lib/lp/registry/services/sharingservice.py |
844 | +++ b/lib/lp/registry/services/sharingservice.py |
845 | @@ -856,7 +856,7 @@ class SharingService: |
846 | |
847 | # Create a job to remove subscriptions for artifacts the grantee can no |
848 | # longer see. |
849 | - return getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
850 | + getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
851 | user, artifacts, grantee=grantee, pillar=pillar) |
852 | |
853 | def ensureAccessGrants(self, grantees, user, bugs=None, branches=None, |
854 | diff --git a/lib/lp/registry/templates/pillar-sharing-details.pt b/lib/lp/registry/templates/pillar-sharing-details.pt |
855 | index dc907dd..d201bd3 100644 |
856 | --- a/lib/lp/registry/templates/pillar-sharing-details.pt |
857 | +++ b/lib/lp/registry/templates/pillar-sharing-details.pt |
858 | @@ -26,21 +26,31 @@ |
859 | <div metal:fill-slot="main"> |
860 | |
861 | <div id="observer-summary"> |
862 | - <p> |
863 | - <tal:bugs replace="view/shared_bugs_count">0</tal:bugs> bugs, |
864 | - <tal:branches replace="view/shared_branches_count">0</tal:branches> Bazaar branches, |
865 | - <tal:gitrepositories replace="view/shared_gitrepositories_count">0</tal:gitrepositories> Git repositories, |
866 | - <tal:snaps replace="view/shared_snaps_count">0</tal:snaps> snaps, |
867 | - and <tal:specifications |
868 | - replace="view/shared_specifications_count">0</tal:specifications> |
869 | - blueprints shared with <tal:name replace="view/person/displayname"> |
870 | - grantee</tal:name>.<br /> |
871 | - |
872 | <tal:is-team condition="view/person/is_team"> |
873 | - <tal:members>3</tal:members> team members can view these bugs, |
874 | - Bazaar branches, Git repositories, and blueprints. |
875 | + <tal:members tal:replace="view/person/active_member_count" /> |
876 | + team members can view these artifacts. |
877 | </tal:is-team> |
878 | - </p> |
879 | + Shared with <tal:name replace="view/person/displayname">grantee</tal:name>: |
880 | + <ul class="bulleted"> |
881 | + <li tal:condition="view/shared_bugs_count"> |
882 | + <span tal:replace="view/shared_bugs_count" /> bugs |
883 | + </li> |
884 | + <li tal:condition="view/shared_branches_count"> |
885 | + <span tal:replace="view/shared_branches_count" /> Bazaar branches |
886 | + </li> |
887 | + <li tal:condition="view/shared_gitrepositories_count"> |
888 | + <span tal:replace="view/shared_gitrepositories_count" /> Git repositories |
889 | + </li> |
890 | + <li tal:condition="view/shared_ocirecipe_count"> |
891 | + <span tal:replace="view/shared_ocirecipe_count" /> OCI recipes |
892 | + </li> |
893 | + <li tal:condition="view/shared_snaps_count"> |
894 | + <span tal:replace="view/shared_snaps_count" /> snap recipes |
895 | + </li> |
896 | + <li tal:condition="view/shared_specifications_count"> |
897 | + <span tal:replace="view/shared_specifications_count" /> blueprints |
898 | + </li> |
899 | + </ul> |
900 | </div> |
901 | |
902 | <table id="shared-table" class="listing sortable"> |
903 | @@ -50,7 +60,8 @@ |
904 | <thead> |
905 | <tr> |
906 | <th colspan="2" width=""> |
907 | - Subscribed Bug Report, Bazaar Branch, Git Repository, or Blueprint |
908 | + Subscribed bug report, Bazaar branch, Git repository, snap recipe, |
909 | + OCI recipe or blueprint |
910 | </th> |
911 | <th> |
912 | Information Type |
913 | @@ -61,7 +72,7 @@ |
914 | <tr> |
915 | <td colspan="3"> |
916 | There are no shared bugs, Bazaar branches, Git repositories, |
917 | - or blueprints. |
918 | + snap recipes, OCI recipes or blueprints. |
919 | </td> |
920 | </tr> |
921 | </tbody> |
Replied all comments. I might worth another quick review round, or at least a validation of the screenshots.