Merge lp:~wallyworld/launchpad/dupe-project-warning-317258 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 15751
Proposed branch: lp:~wallyworld/launchpad/dupe-project-warning-317258
Merge into: lp:launchpad
Diff against target: 240 lines (+82/-18)
8 files modified
lib/lp/bugs/browser/tests/test_bugs.py (+16/-3)
lib/lp/bugs/interfaces/malone.py (+3/-2)
lib/lp/bugs/javascript/bug_picker.js (+5/-2)
lib/lp/bugs/javascript/duplicates.js (+26/-0)
lib/lp/bugs/javascript/tests/test_bug_picker.js (+10/-4)
lib/lp/bugs/javascript/tests/test_duplicates.js (+14/-0)
lib/lp/code/javascript/tests/test_bugspeclinks.html (+2/-5)
lib/lp/systemhomes.py (+6/-2)
To merge this branch: bzr merge lp:~wallyworld/launchpad/dupe-project-warning-317258
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+118287@code.launchpad.net

Commit message

Display a warning if a user wants to select a duplicate bug affecting a different project.

Description of the change

== Implementation ==

When a bug search is done for the bug picker, a new optional related_bug parameter is passed in. The json search results data contains a new 'different_pillars' attribute which is true if the search result bug and related bug do not have any target pillars in common.

The ui displays a warning if the bug data says that the proposed dupe and the context bug have no pillars in common.

== Demo and QA ==

http://people.canonical.com/~ianb/different-project-warning.png
http://people.canonical.com/~ianb/different-project-warning2.png

== Tests ==

Enhance bug picker and duplicate bug yui tests.
Enhance getBugData() unit tests.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/systemhomes.py
  lib/lp/bugs/browser/tests/test_bugs.py
  lib/lp/bugs/interfaces/malone.py
  lib/lp/bugs/javascript/bug_picker.js
  lib/lp/bugs/javascript/duplicates.js
  lib/lp/bugs/javascript/tests/test_bug_picker.js
  lib/lp/bugs/javascript/tests/test_duplicates.js

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

I think the code is good, as is the text. I don't think we want two warning messages to have different font weights. I don't think we need to make the message bold.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/tests/test_bugs.py'
--- lib/lp/bugs/browser/tests/test_bugs.py 2012-07-27 08:01:54 +0000
+++ lib/lp/bugs/browser/tests/test_bugs.py 2012-08-07 05:02:24 +0000
@@ -117,16 +117,19 @@
117 # we should get some valid content out of this117 # we should get some valid content out of this
118 self.assertIn('Search all bugs', content)118 self.assertIn('Search all bugs', content)
119119
120 def test_getBugData(self):120 def _assert_getBugData(self, related_bug=None):
121 # The getBugData method works as expected.121 # The getBugData method works as expected.
122 owner = self.factory.makePerson()122 owner = self.factory.makePerson()
123 product = self.factory.makeProduct()
123 bug = self.factory.makeBug(124 bug = self.factory.makeBug(
125 product=product,
124 owner=owner,126 owner=owner,
125 status=BugTaskStatus.INPROGRESS,127 status=BugTaskStatus.INPROGRESS,
126 title='title', description='description',128 title='title', description='description',
127 information_type=InformationType.PRIVATESECURITY)129 information_type=InformationType.PRIVATESECURITY)
128 with person_logged_in(owner):130 with person_logged_in(owner):
129 bug_data = getUtility(IMaloneApplication).getBugData(owner, bug.id)131 bug_data = getUtility(IMaloneApplication).getBugData(
132 owner, bug.id, related_bug)
130 expected_bug_data = {133 expected_bug_data = {
131 'id': bug.id,134 'id': bug.id,
132 'information_type': 'Private Security',135 'information_type': 'Private Security',
@@ -137,6 +140,16 @@
137 'status_class': 'statusINPROGRESS',140 'status_class': 'statusINPROGRESS',
138 'bug_summary': 'title',141 'bug_summary': 'title',
139 'description': 'description',142 'description': 'description',
140 'bug_url': canonical_url(bug.default_bugtask)143 'bug_url': canonical_url(bug.default_bugtask),
144 'different_pillars': related_bug is not None
141 }145 }
142 self.assertEqual([expected_bug_data], bug_data)146 self.assertEqual([expected_bug_data], bug_data)
147
148 def test_getBugData(self):
149 # The getBugData method works as expected without a related_bug.
150 self._assert_getBugData()
151
152 def test_getBugData_with_related_bug(self):
153 # The getBugData method works as expected if related bug is specified.
154 related_bug = self.factory.makeBug()
155 self._assert_getBugData(related_bug)
143156
=== modified file 'lib/lp/bugs/interfaces/malone.py'
--- lib/lp/bugs/interfaces/malone.py 2012-07-27 08:01:54 +0000
+++ lib/lp/bugs/interfaces/malone.py 2012-08-07 05:02:24 +0000
@@ -41,11 +41,12 @@
4141
42 @call_with(user=REQUEST_USER)42 @call_with(user=REQUEST_USER)
43 @operation_parameters(43 @operation_parameters(
44 bug_id=copy_field(IBug['id'])44 bug_id=copy_field(IBug['id']),
45 related_bug=Reference(schema=IBug)
45 )46 )
46 @export_read_operation()47 @export_read_operation()
47 @operation_for_version('devel')48 @operation_for_version('devel')
48 def getBugData(user, bug_id):49 def getBugData(user, bug_id, related_bug=None):
49 """Search bugtasks matching the specified criteria.50 """Search bugtasks matching the specified criteria.
5051
51 The only criteria currently supported is to search for a bugtask with52 The only criteria currently supported is to search for a bugtask with
5253
=== modified file 'lib/lp/bugs/javascript/bug_picker.js'
--- lib/lp/bugs/javascript/bug_picker.js 2012-08-03 12:47:11 +0000
+++ lib/lp/bugs/javascript/bug_picker.js 2012-08-07 05:02:24 +0000
@@ -116,7 +116,10 @@
116 = Y.lp.client.append_qs("", "ws.accept", "application.json");116 = Y.lp.client.append_qs("", "ws.accept", "application.json");
117 qs_data = Y.lp.client.append_qs(qs_data, "ws.op", "getBugData");117 qs_data = Y.lp.client.append_qs(qs_data, "ws.op", "getBugData");
118 qs_data = Y.lp.client.append_qs(qs_data, "bug_id", bug_id);118 qs_data = Y.lp.client.append_qs(qs_data, "bug_id", bug_id);
119119 if (Y.Lang.isValue(LP.cache.bug)) {
120 qs_data = Y.lp.client.append_qs(
121 qs_data, "related_bug", LP.cache.bug.self_link);
122 }
120 var that = this;123 var that = this;
121 var config = {124 var config = {
122 on: {125 on: {
@@ -152,7 +155,7 @@
152 // Template for rendering the bug details.155 // Template for rendering the bug details.
153 _bug_details_template: function() {156 _bug_details_template: function() {
154 return [157 return [
155 '<table><tbody><tr><td>',158 '<table class="confirm-bug-details"><tbody><tr><td>',
156 '<div id="client-listing">',159 '<div id="client-listing">',
157 ' <div class="buglisting-col1">',160 ' <div class="buglisting-col1">',
158 ' <div class="importance {{importance_class}}">',161 ' <div class="importance {{importance_class}}">',
159162
=== modified file 'lib/lp/bugs/javascript/duplicates.js'
--- lib/lp/bugs/javascript/duplicates.js 2012-08-03 12:47:11 +0000
+++ lib/lp/bugs/javascript/duplicates.js 2012-08-07 05:02:24 +0000
@@ -41,6 +41,32 @@
41 });41 });
42 },42 },
4343
44 _syncResultsUI: function() {
45 Y.lp.bugs.bug_picker.BugPicker.prototype._syncResultsUI.call(this);
46 // Display warning about different project if required.
47 var bug_data = this.get('results');
48 if (!bug_data.length) {
49 this._hide_bug_results();
50 return;
51 }
52 bug_data = bug_data[0];
53 if (!bug_data.different_pillars) {
54 return;
55 }
56 var bug_details_table = this._results_box.one(
57 'table.confirm-bug-details tbody');
58 var different_project_warning =
59 '<p id="different-project-warning" ' +
60 'class="block-sprite large-warning">' +
61 'This bug affects a different project to the bug you ' +
62 'are specifying here.' +
63 '</p>';
64 var warning_node_row = Y.Node.create('<tr></tr>');
65 warning_node_row.appendChild(
66 Y.Node.create('<td></td>').setContent(different_project_warning));
67 bug_details_table.appendChild(warning_node_row);
68 },
69
44 _bug_search_header: function() {70 _bug_search_header: function() {
45 var search_header = '<p class="search-header">' +71 var search_header = '<p class="search-header">' +
46 'Marking this bug as a duplicate will, ' +72 'Marking this bug as a duplicate will, ' +
4773
=== modified file 'lib/lp/bugs/javascript/tests/test_bug_picker.js'
--- lib/lp/bugs/javascript/tests/test_bug_picker.js 2012-08-03 12:47:11 +0000
+++ lib/lp/bugs/javascript/tests/test_bug_picker.js 2012-08-07 05:02:24 +0000
@@ -17,10 +17,15 @@
17 Y.Assert.areEqual(17 Y.Assert.areEqual(
18 'file:///api/devel/bugs',18 'file:///api/devel/bugs',
19 this.mockio.last_request.url);19 this.mockio.last_request.url);
20 Y.Assert.areEqual(20 var expected_data =
21 this.mockio.last_request.config.data,
22 'ws.accept=application.json&ws.op=getBugData&' +21 'ws.accept=application.json&ws.op=getBugData&' +
23 'bug_id=' + bug_id);22 'bug_id=' + bug_id;
23 if (Y.Lang.isValue(LP.cache.bug)) {
24 var bug_uri = encodeURIComponent('api/devel/bugs/1');
25 expected_data += '&related_bug=' + bug_uri;
26 }
27 Y.Assert.areEqual(
28 this.mockio.last_request.config.data, expected_data);
24 } else {29 } else {
25 Y.Assert.areEqual(30 Y.Assert.areEqual(
26 '/api/devel/bugs/1', this.mockio.last_request.url);31 '/api/devel/bugs/1', this.mockio.last_request.url);
@@ -47,7 +52,8 @@
47 bug_summary: 'dupe title',52 bug_summary: 'dupe title',
48 is_private: is_private,53 is_private: is_private,
49 duplicate_of_link: 'api/devel/bugs/' + bug_id,54 duplicate_of_link: 'api/devel/bugs/' + bug_id,
50 self_link: 'api/devel/bugs/' + bug_id}];55 self_link: 'api/devel/bugs/' + bug_id,
56 different_pillars: this.different_pillars}];
51 this.mockio.last_request.successJSON(expected_updated_entry);57 this.mockio.last_request.successJSON(expected_updated_entry);
52 this._assert_form_state(true);58 this._assert_form_state(true);
53 },59 },
5460
=== modified file 'lib/lp/bugs/javascript/tests/test_duplicates.js'
--- lib/lp/bugs/javascript/tests/test_duplicates.js 2012-08-02 13:52:58 +0000
+++ lib/lp/bugs/javascript/tests/test_duplicates.js 2012-08-07 05:02:24 +0000
@@ -127,6 +127,20 @@
127 this._assert_form_state(false);127 this._assert_form_state(false);
128 },128 },
129129
130 // A warning is displayed for search results which are not targeted to
131 // the same project.
132 test_different_pillars: function() {
133 this.widget = this._createWidget(false);
134 this.different_pillars = true;
135 this._assert_search_form_submission(4);
136 this._assert_search_form_success(4);
137 var privacy_message = Y.one('#different-project-warning');
138 Y.Assert.areEqual(
139 'This bug affects a different project to the bug ' +
140 'you are specifying here.',
141 privacy_message.get('text').trim());
142 },
143
130 // The expected data is submitted after searching for and selecting a144 // The expected data is submitted after searching for and selecting a
131 // bug.145 // bug.
132 _assert_dupe_submission: function(bug_id) {146 _assert_dupe_submission: function(bug_id) {
133147
=== modified file 'lib/lp/code/javascript/tests/test_bugspeclinks.html'
--- lib/lp/code/javascript/tests/test_bugspeclinks.html 2012-08-03 12:47:11 +0000
+++ lib/lp/code/javascript/tests/test_bugspeclinks.html 2012-08-07 05:02:24 +0000
@@ -61,11 +61,8 @@
61 <div id="fixture">61 <div id="fixture">
62 </div>62 </div>
63 <script type="text/x-template" id="bugspec-links">63 <script type="text/x-template" id="bugspec-links">
64 <a href="#" class="pick-bug js-action">Select a bug</a>
65 </script>
66 <div>
67 <div id="buglinks" class="actions">64 <div id="buglinks" class="actions">
68 <a href="https://foo/+linkbug" id="linkbug">65 <a href="#" id="linkbug" class="pick-bug js-action">
69 Link to bug report</a>66 Link to bug report</a>
70 <div id="buglink-list">67 <div id="buglink-list">
71 <a id="delete-buglink-6"68 <a id="delete-buglink-6"
@@ -73,6 +70,6 @@
73 class="delete-buglink" title="Remove link"></a>70 class="delete-buglink" title="Remove link"></a>
74 </div>71 </div>
75 </div>72 </div>
76 </div>73 </script>
77 </body>74 </body>
78</html>75</html>
7976
=== modified file 'lib/lp/systemhomes.py'
--- lib/lp/systemhomes.py 2012-07-27 08:01:54 +0000
+++ lib/lp/systemhomes.py 2012-08-07 05:02:24 +0000
@@ -128,7 +128,7 @@
128 """See `IMaloneApplication`."""128 """See `IMaloneApplication`."""
129 return getUtility(IBugTaskSet).search(search_params)129 return getUtility(IBugTaskSet).search(search_params)
130130
131 def getBugData(self, user, bug_id):131 def getBugData(self, user, bug_id, related_bug=None):
132 """See `IMaloneApplication`."""132 """See `IMaloneApplication`."""
133 search_params = BugTaskSearchParams(user, bug=bug_id)133 search_params = BugTaskSearchParams(user, bug=bug_id)
134 bugtasks = getUtility(IBugTaskSet).search(search_params)134 bugtasks = getUtility(IBugTaskSet).search(search_params)
@@ -138,6 +138,9 @@
138 data = []138 data = []
139 for bug in bugs:139 for bug in bugs:
140 bugtask = bug.default_bugtask140 bugtask = bug.default_bugtask
141 different_pillars = related_bug and (
142 set(bug.affected_pillars).isdisjoint(
143 related_bug.affected_pillars)) or False
141 data.append({144 data.append({
142 'id': bug_id,145 'id': bug_id,
143 'information_type': bug.information_type.title,146 'information_type': bug.information_type.title,
@@ -149,7 +152,8 @@
149 'status_class': 'status' + bugtask.status.name,152 'status_class': 'status' + bugtask.status.name,
150 'bug_summary': bug.title,153 'bug_summary': bug.title,
151 'description': bug.description,154 'description': bug.description,
152 'bug_url': canonical_url(bugtask)})155 'bug_url': canonical_url(bugtask),
156 'different_pillars': different_pillars})
153 return data157 return data
154158
155 def createBug(self, owner, title, description, target,159 def createBug(self, owner, title, description, target,