Merge lp:~psivaa/uci-engine/find-missing-mps into lp:uci-engine

Proposed by Para Siva
Status: Needs review
Proposed branch: lp:~psivaa/uci-engine/find-missing-mps
Merge into: lp:uci-engine
Diff against target: 539 lines (+338/-54)
3 files modified
tests/test_webui.py (+190/-47)
webui/common/static/common/webui.css (+43/-0)
webui/tickets/static/tickets/webuiforms.js (+105/-7)
To merge this branch: bzr merge lp:~psivaa/uci-engine/find-missing-mps
Reviewer Review Type Date Requested Status
Francis Ginther Needs Fixing
Review via email: mp+245044@code.launchpad.net

Commit message

Warning the user about removal of MP's and Sources from subtickets.

Description of the change

This is the first part of stray sources and MP removal task, to warn the user about missing sources and MP's whilst editing. The warning comes as a modal panel with missing entries and for now clicking 'OK' on the panel will complete the editing but will not actually remove the entries from the subtickets. This will be done in a subsequent MP.

To post a comment you must log in.
Revision history for this message
Francis Ginther (fginther) wrote :

Most of my comments are cosmetic and refactoring suggestions. The two that I'd like some more feedback on are the test cases. These tests modify the field contents, but I don't see that anything is done to verify that it had any effect or the warning message was accurate. They appear to be useful tests otherwise, just missing an assert on the warning message.

I do see the goal of this MP and the desire to put up a warning. It makes sense and I think is ultimately more user friendly then silently deleting them as the spreadsheet would do.

review: Needs Fixing
917. By Para Siva

Check for the content of warning message

Revision history for this message
Francis Ginther (fginther) wrote :

These tests are improved, thanks.

I'm now hitting a test failure in test_edit_ticket_sources_and_mps(), which only performs a ticket edit test and relies on a prior test to create the ticket. These new tests introduce changes to the last ticket created and leads to failure in test_edit_ticket_sources_and_mps(). Also, with all of the MPs that have been targeting this file, it's been constantly churning and the order of tests changing. At a minimum test_edit_ticket_sources_and_mps() needs to be updated, a better solution to remove dependencies between tests would be to refactor these into idempotent tests. (The tests being added here are idempotent).

The pep8 error needs to be fixed in this MP, but test_edit_ticket_sources_and_mps() could be fixed in another MP. I may end up doing some refactoring if I find some spare time as I try to update some other MPs I'm trying to land, so let's coordinate. I'm tentatively approving so as not to hold up tarmac if we refactor that test in another MP and the pep8 error gets resolved.

review: Approve
Revision history for this message
Francis Ginther (fginther) wrote :

Argh! I just realized that the required fields change landed this week and the two tests need to be updated with the latest trunk. 'owner' is no longer a required field, 'landers', 'test_notes' and one of ['sources', 'merge_proposals'] are now required.

Again, we may be able to coordinate on refactoring these tests.

review: Needs Fixing
918. By Para Siva

Refactor code, make old tests idempotent before merging trunk

919. By Para Siva

Merging trunk and resolving conflict before fixing the tests

920. By Para Siva

Fixing tests after merging trunk

Revision history for this message
Para Siva (psivaa) wrote :

Thanks for the review. Rev 920 has the fixes to the comments on rev916 and it also fixes to the tests after merging with trunk where certain fields made 'required'.

Revision history for this message
Para Siva (psivaa) wrote :

917 was an intermediate push :), but thanks again for the reviews. r920 is the latest.

Unmerged revisions

920. By Para Siva

Fixing tests after merging trunk

919. By Para Siva

Merging trunk and resolving conflict before fixing the tests

918. By Para Siva

Refactor code, make old tests idempotent before merging trunk

917. By Para Siva

Check for the content of warning message

916. By Para Siva

Add tests make them pas

915. By Para Siva

some fancy refactoring

914. By Para Siva

Making panel look saner

913. By Para Siva

Add a header to the panel

912. By Para Siva

Include source packages for the missing

911. By Para Siva

Remove assignable conflict

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/test_webui.py' (properties changed: +x to -x)
2--- tests/test_webui.py 2014-12-18 21:37:15 +0000
3+++ tests/test_webui.py 2015-01-04 15:25:56 +0000
4@@ -249,6 +249,24 @@
5 [s.text.splitlines() for s in subtickets])
6
7 def test_edit_ticket(self):
8+ go_to('/create')
9+ wait_for(get_element, tag='form')
10+
11+ # Submit the form with the required fields.
12+ title = get_element_by_css('input[name="title"]')
13+ title_text = 'Editing with Invalid MPs at {}'.format(time.time())
14+ write_textfield(title, title_text)
15+ landers = get_element_by_css('input[name="landers"]')
16+ write_textfield(landers, 'foo@bar.com')
17+ test_notes = get_element_by_css('textarea[name="test_notes"]')
18+ write_textfield(test_notes, 'test this')
19+ sources = get_element_by_css('textarea[name="sources"]')
20+ write_textfield(sources, 'foo, , , bar\nbang , ')
21+ submit = get_element_by_css('input[type="submit"]')
22+ click_button(submit, wait=True)
23+ # Redirects to the ticket page.
24+ ticket_title = wait_for(get_element, tag='h2')
25+ self.assertEqual(title_text, ticket_title.text)
26 # Edit a ticket via WebUI form.
27 go_to('/')
28
29@@ -313,6 +331,24 @@
30 fails(get_element_by_css, 'a.bug-link')
31
32 def test_edit_ticket_sources_and_mps(self):
33+ go_to('/create')
34+ wait_for(get_element, tag='form')
35+
36+ # Submit the form with the required fields.
37+ title = get_element_by_css('input[name="title"]')
38+ title_text = 'Editing with Invalid MPs at {}'.format(time.time())
39+ write_textfield(title, title_text)
40+ landers = get_element_by_css('input[name="landers"]')
41+ write_textfield(landers, 'foo@bar.com')
42+ test_notes = get_element_by_css('textarea[name="test_notes"]')
43+ write_textfield(test_notes, 'test this')
44+ sources = get_element_by_css('textarea[name="sources"]')
45+ write_textfield(sources, 'foo, , , bar\nbang , ')
46+ submit = get_element_by_css('input[type="submit"]')
47+ click_button(submit, wait=True)
48+ # Redirects to the ticket page.
49+ ticket_title = wait_for(get_element, tag='h2')
50+ self.assertEqual(title_text, ticket_title.text)
51 # Edit a ticket sources & MPs.
52 go_to('/')
53
54@@ -330,7 +366,7 @@
55 title = get_element_by_css('input[name="title"]')
56 write_textfield(title, new_title)
57 sources = get_element_by_css('textarea[name="sources"]')
58- write_textfield(sources, 'boing')
59+ write_textfield(sources, 'boing, foo, bar, bang')
60 mps = get_element_by_css('textarea[name="merge_proposals"]')
61 write_textfield(
62 mps,
63@@ -345,49 +381,41 @@
64 self.assertEqual(new_title, ticket_title.text)
65 subtickets = get_elements_by_css('table.subticket')
66 self.assertEqual(
67- [['Source: foo',
68- 'Status: -',
69- 'Step: New',
70- 'Current version: Not available',
71- 'MPs: https://code.launchpad.net/~x/foo/a_branch/+merge/1',
72- 'https://code.launchpad.net/~x/foo/a_branch/+merge/2',
73- 'Artifacts:',
74- 'Conflicts:'],
75- ['Source: bar',
76- 'Status: -',
77- 'Step: New',
78- 'Current version: Not available',
79- 'MPs:',
80- 'Artifacts:',
81- 'Conflicts:'],
82- ['Source: bang',
83- 'Status: -',
84- 'Step: New',
85- 'Current version: Not available',
86- 'MPs:',
87- 'Artifacts:',
88- 'Conflicts:'],
89- ['Source: baz',
90- 'Status: -',
91- 'Step: New',
92- 'Current version: Not available',
93- 'MPs: https://code.launchpad.net/~x/baz/a_branch/+merge/3',
94- 'Artifacts:',
95- 'Conflicts:'],
96- ['Source: boing',
97- 'Status: -',
98- 'Step: New',
99- 'Current version: Not available',
100- 'MPs:',
101- 'Artifacts:',
102- 'Conflicts:'],
103- ['Source: zoing',
104- 'Status: -',
105- 'Step: New',
106- 'Current version: Not available',
107- 'MPs: https://code.launchpad.net/~x/zoing/a_branch/+merge/1',
108- 'Artifacts:',
109- 'Conflicts:']],
110+ [[u'Source: foo',
111+ u'Status: -',
112+ u'Step: New',
113+ u'Current version: Not available',
114+ u'MPs:',
115+ u'Artifacts:',
116+ u'Conflicts:'],
117+ [u'Source: bar',
118+ u'Status: -',
119+ u'Step: New',
120+ u'Current version: Not available',
121+ u'MPs:',
122+ u'Artifacts:',
123+ u'Conflicts:'],
124+ [u'Source: bang',
125+ u'Status: -',
126+ u'Step: New',
127+ u'Current version: Not available',
128+ u'MPs:',
129+ u'Artifacts:',
130+ u'Conflicts:'],
131+ [u'Source: boing',
132+ u'Status: -',
133+ u'Step: New',
134+ u'Current version: Not available',
135+ u'MPs:',
136+ u'Artifacts:',
137+ u'Conflicts:'],
138+ [u'Source: zoing',
139+ u'Status: -',
140+ u'Step: New',
141+ u'Current version: Not available',
142+ u'MPs: https://code.launchpad.net/~x/zoing/a_branch/+merge/1',
143+ u'Artifacts:',
144+ u'Conflicts:']],
145 [s.text.splitlines() for s in subtickets])
146
147 def test_create_first_with_invalid_mps(self):
148@@ -397,7 +425,7 @@
149
150 # Submit the form with the required fields.
151 title = get_element_by_css('input[name="title"]')
152- title_text = 'Testing Invalid MPs at {}'.format(time.asctime())
153+ title_text = 'Testing Invalid MPs at {}'.format(time.time())
154 write_textfield(title, title_text)
155 landers = get_element_by_css('input[name="landers"]')
156 write_textfield(landers, 'foo@bar.com')
157@@ -434,7 +462,7 @@
158
159 # Submit the form with the required fields.
160 title = get_element_by_css('input[name="title"]')
161- title_text = 'Editing with Invalid MPs at {}'.format(time.asctime())
162+ title_text = 'Editing with Invalid MPs at {}'.format(time.time())
163 write_textfield(title, title_text)
164 landers = get_element_by_css('input[name="landers"]')
165 write_textfield(landers, 'foo@bar.com')
166@@ -462,7 +490,7 @@
167
168 # Update the ticket with a unique title and distinct sources
169 # and merge proposals.
170- new_title = 'Edited for testing on {}!'.format(time.asctime())
171+ new_title = 'Edited for testing on {}!'.format(time.time())
172 title = get_element_by_css('input[name="title"]')
173 write_textfield(title, new_title)
174 sources = get_element_by_css('textarea[name="sources"]')
175@@ -478,6 +506,121 @@
176 'The entry https://bazaar.launchpad.net/~x/zoing/a_branch/+merge/5'
177 ' is not a valid MP URL.', error.text)
178
179+ def test_edit_by_removing_mps(self):
180+ # Create a ticket using sources and MPs.
181+ go_to('/create')
182+ wait_for(get_element, tag='form')
183+
184+ # Submit the form with the required fields.
185+ title_text = 'Editing with Invalid MPs at {}'.format(time.time())
186+ title = get_element_by_css('input[name="title"]')
187+ title_text = 'Remove MP test at {}'.format(time.time())
188+ write_textfield(title, title_text)
189+ landers = get_element_by_css('input[name="landers"]')
190+ write_textfield(landers, 'foo@bar.com')
191+ sources = get_element_by_css('textarea[name="sources"]')
192+ write_textfield(sources, 'testsource')
193+ test_notes = get_element_by_css('textarea[name="test_notes"]')
194+ write_textfield(test_notes, 'test this')
195+ mps = get_element_by_css('textarea[name="merge_proposals"]')
196+ write_textfield(
197+ mps,
198+ 'https://code.launchpad.net/~x/foo/a_branch/+merge/1\n'
199+ 'https://code.launchpad.net/~x/foo/a_branch/+merge/2\n'
200+ 'https://code.launchpad.net/~x/baz/a_branch/+merge/3')
201+ submit = get_element_by_css('input[type="submit"]')
202+ click_button(submit, wait=True)
203+ # Redirects to the ticket page.
204+ ticket_title = wait_for(get_element, tag='h2')
205+ self.assertEqual(title_text, ticket_title.text)
206+
207+ # Edit a ticket sources & MPs.
208+ go_to('/')
209+
210+ # Edit the first available ticket.
211+ wait_for(get_element_by_css, 'a.yui3-pagview-link-page-active')
212+ a_ticket_link = get_elements_by_css('a.row_link')[0]
213+ a_ticket_link.click()
214+ edit_link = wait_for(get_element_by_css, 'a.edit-link')
215+ edit_link.click()
216+ wait_for(get_element, tag='form')
217+
218+ # Update the ticket with a unique title and distinct sources
219+ # and merge proposals.
220+ new_title = 'Edited for testing on {}!'.format(time.time())
221+ title = get_element_by_css('input[name="title"]')
222+ write_textfield(title, new_title)
223+ mps = get_element_by_css('textarea[name="merge_proposals"]')
224+ write_textfield(
225+ mps,
226+ 'https://code.launchpad.net/~x/foo/a_branch/+merge/1\n'
227+ 'https://code.launchpad.net/~x/baz/a_branch/+merge/3\n'
228+ 'https://code.launchpad.net/~x/baz/a_branch/+merge/4')
229+ submit = get_element_by_css('input[type="submit"]')
230+ click_button(submit, wait=True)
231+ warn_bd = wait_for(get_element_by_css, '.yui3-widget-bd')
232+ self.assertIn('https://code.launchpad.net/~x/foo/a_branch/+merge/2',
233+ warn_bd.text)
234+ ok_link = get_elements_by_css('.yui3-button')[1]
235+ ok_link.click()
236+ ticket_title = wait_for(get_element, tag='h2')
237+ self.assertEqual(new_title, ticket_title.text)
238+
239+ def test_edit_by_removing_sources(self):
240+ # Create a ticket using sources and MPs.
241+ go_to('/create')
242+ wait_for(get_element, tag='form')
243+
244+ # Submit the form with the required fields.
245+ title = get_element_by_css('input[name="title"]')
246+ title_text = 'Remove MP test at {}'.format(time.time())
247+ write_textfield(title, title_text)
248+ owner = get_element_by_css('input[name="landers"]')
249+ write_textfield(owner, 'foo@bar.com')
250+ test_notes = get_element_by_css('textarea[name="test_notes"]')
251+ write_textfield(test_notes, 'test this')
252+ sources = get_element_by_css('textarea[name="sources"]')
253+ write_textfield(sources,
254+ 'boing\n'
255+ 'doing\n'
256+ 'going\n'
257+ 'noing')
258+ submit = get_element_by_css('input[type="submit"]')
259+ click_button(submit, wait=True)
260+ # Redirects to the ticket page.
261+ ticket_title = wait_for(get_element, tag='h2')
262+ self.assertEqual(title_text, ticket_title.text)
263+
264+ # Edit a ticket sources & MPs.
265+ go_to('/')
266+
267+ # Edit the first available ticket.
268+ wait_for(get_element_by_css, 'a.yui3-pagview-link-page-active')
269+ a_ticket_link = get_elements_by_css('a.row_link')[0]
270+ a_ticket_link.click()
271+ edit_link = wait_for(get_element_by_css, 'a.edit-link')
272+ edit_link.click()
273+ wait_for(get_element, tag='form')
274+
275+ # Update the ticket with a unique title and distinct sources
276+ # and merge proposals.
277+ new_title = 'Edited for testing on {}!'.format(time.time())
278+ title = get_element_by_css('input[name="title"]')
279+ write_textfield(title, new_title)
280+ sources = get_element_by_css('textarea[name="sources"]')
281+ write_textfield(sources,
282+ 'boing\n'
283+ 'doing\n'
284+ 'noing')
285+ submit = get_element_by_css('input[type="submit"]')
286+ click_button(submit, wait=True)
287+ warn_bd = wait_for(get_element_by_css, '.yui3-widget-bd')
288+ self.assertIn('going', warn_bd.text)
289+ ok_link = get_elements_by_css('.yui3-button')[1]
290+ ok_link.click()
291+ ticket_title = wait_for(get_element, tag='h2')
292+ self.assertEqual(new_title, ticket_title.text)
293+
294 def test_json_status(self):
295 # Ensure the webui charm is providing us a valid status_urls.json.
296 go_to('/static/engine-health/status_urls.json')
297
298=== modified file 'webui/common/static/common/webui.css'
299--- webui/common/static/common/webui.css 2014-11-12 03:18:24 +0000
300+++ webui/common/static/common/webui.css 2015-01-04 15:25:56 +0000
301@@ -132,6 +132,48 @@
302 padding: 0px;
303 }
304
305+.yui3-panel {
306+ outline: 1px solid;
307+ background-color: white;
308+ color: black;
309+ text-align: center;
310+ margin: 10px 15px 15px 20px;
311+ padding: 5px;
312+ vertical-align: baseline;
313+}
314+
315+.yui3-widget-bd{
316+ background-color: white;
317+ color: black;
318+ text-align: center;
319+ margin: 10px 15px 15px 20px;
320+ padding: 5px;
321+ vertical-align: baseline;
322+}
323+
324+.yui3-widget-hd{
325+ font-weight: bold;
326+ text-align: center;
327+ color: white;
328+ background-color: #c03f11;
329+}
330+
331+.yui3-widget-ft{
332+ text-align: center;
333+ margin: 0px;
334+ padding: 0px;
335+ border: 0px none;
336+ vertical-align: baseline;
337+}
338+
339+.yui3-widget-buttons{
340+ float: center;
341+ position: relative;
342+ width:200px;
343+ border: 5px none;
344+ vertical-align: baseline;
345+}
346+
347 #refresh {
348 padding: 5px;
349 }
350@@ -366,6 +408,7 @@
351 text-align: left;
352 }
353
354+
355 /* General links decorators. */
356 a.image-link {
357 background-image: url('/static/common/cdrom.svg');
358
359=== modified file 'webui/tickets/static/tickets/webuiforms.js'
360--- webui/tickets/static/tickets/webuiforms.js 2014-12-18 20:27:04 +0000
361+++ webui/tickets/static/tickets/webuiforms.js 2015-01-04 15:25:56 +0000
362@@ -32,7 +32,7 @@
363 {label : 'Trusty - 14.04', value : 'trusty'},
364 {label : 'Precise - 12.04', value : 'precise'}
365 ],
366-
367+
368 /**
369 * Convert form 'sources' and 'merge_proposals' to 'subtickets'.
370 *
371@@ -221,7 +221,7 @@
372 *
373 * Positive int32 except ZERO (1 - 2147483648)
374 *
375- * @method setupEditForm
376+ * @method validateBugId
377 */
378 validateBugId: function(val, field) {
379 var filter = /^[\d]*$/;
380@@ -256,6 +256,46 @@
381 },
382
383 /**
384+ * Find missing MP entries.
385+ *
386+ * @method findMissingMps
387+ */
388+ findMissingMps: function(ticket_data, form_data) {
389+ var new_mps = [];
390+ form_data['subtickets'].forEach( function(f) {
391+ if (f['merge_proposals']){
392+ f['merge_proposals'].forEach (function (m) {
393+ new_mps.push(m['lp_url']);
394+ });
395+ }
396+ });
397+ return Y.webuiforms.findMissed(ticket_data, new_mps);
398+ },
399+
400+ /**
401+ * Find findMissingSources
402+ */
403+ findMissingSources: function(ticket_data, form_data) {
404+ var new_sources = [];
405+ form_data['subtickets'].forEach( function(f) {
406+ new_sources.push(f['sourcepackage']['name']);
407+ });
408+ return Y.webuiforms.findMissed(ticket_data, new_sources);
409+ },
410+
411+ /**
412+ * Compare two arrays and return missing entries.
413+ *
414+ * @method findMissed
415+ */
416+ findMissed: function(ticket_data, form_data) {
417+ Array.prototype.diff = function(a) {
418+ return this.filter(function(i) {return a.indexOf(i) < 0;});
419+ };
420+ return ticket_data.diff(form_data);
421+ },
422+
423+ /**
424 * Setup a form for editing a ticket.
425 *
426 * @method setupEditForm
427@@ -294,6 +334,48 @@
428 ]
429 });
430
431+ var dialog = new Y.Panel({
432+ contentBox : Y.Node.create('<div id="dialog" />'),
433+ headerContent: 'Content removal warning',
434+ bodyContent: '<div class="message icon-warn">Removal warning</div>',
435+ width : 610,
436+ zIndex : 6,
437+ centered : true,
438+ modal : false,
439+ render : true,
440+ visible : false,
441+ buttons : {
442+ footer: [
443+ {
444+ name : 'cancel',
445+ label : 'Cancel',
446+ action: 'onCancel'
447+ },
448+
449+ {
450+ name : 'proceed',
451+ label : 'OK',
452+ action : 'onOK'
453+ }
454+ ]
455+ }
456+ });
457+
458+ dialog.onCancel = function (e) {
459+ e.preventDefault();
460+ this.hide();
461+ }
462+
463+ dialog.onOK = function (e) {
464+ e.preventDefault();
465+ this.hide();
466+ // XXX psivaa 20141216: accept and do nothing for now
467+ Y.config.win.location.href = f.get(
468+ 'action').replace('/api/v1', '');
469+ }
470+
471+ var mps = [];
472+ var sources = [];
473 // Load ticket information from TS and pre-fill the form.
474 Y.io('/api/v1/fullticket/' + ticket_id + '/', {
475 sync: true,
476@@ -313,14 +395,12 @@
477 'value', (ticket[name] || '').toString());
478 break;
479 case 'sources':
480- var sources = [];
481 ticket.subticket.forEach( function(s) {
482 sources.push(s.sourcepackage.name);
483 });
484 field.set('value', sources.join('\n'));
485 break;
486 case 'merge_proposals':
487- var mps = [];
488 ticket.subticket.forEach( function(s) {
489 s.merge_proposals.forEach( function(mp) {
490 mps.push(mp.lp_url);
491@@ -350,6 +430,7 @@
492
493 Y.one('input[name="edit-button"]').on('click', function(e) {
494 e.halt();
495+
496 if (!f._runValidation()) {
497 return 1;
498 }
499@@ -375,6 +456,9 @@
500 form_data['bug_id'] = null;
501 }
502
503+ var missingMps = Y.webuiforms.findMissingMps(mps, form_data);
504+ var missingSources = Y.webuiforms.findMissingSources(sources, form_data);
505+
506 Y.io(f.get('action'), {
507 // XXX cprov 20141204: we only use and support PUT
508 // because of a phantomjs bug (see TicketResource
509@@ -388,8 +472,22 @@
510 },
511 on: {
512 success: function(tx, r) {
513- Y.config.win.location.href = f.get(
514- 'action').replace('/api/v1', '');
515+ if (missingSources.length || missingMps.length ){
516+ var theMessage = "<p><b>The following have been removed from the original ticket</b></p>";
517+ if(missingSources.length)
518+ theMessage += "<p><b>Source(s):</b></p>" + "<p>" + missingSources.join("<br>") + "</p>";
519+
520+ if(missingMps.length)
521+ theMessage += "<p><b>MP(s):</b></p> " + "<p>" + missingMps.join("<br>") + "</p>";
522+
523+ theMessage += "<p><b> Click OK to accept</b></p>";
524+ Y.one('#dialog .message').setHTML(theMessage);
525+ dialog.show();
526+ }
527+ else {
528+ Y.config.win.location.href = f.get(
529+ 'action').replace('/api/v1', '');
530+ }
531 },
532 failure: function (tx, r) {
533 Y.log('FAILURE: ' + r.responseText);
534@@ -403,4 +501,4 @@
535 }
536 };
537 }, '0.0.1', {requires: ['io', 'node', 'json-parse', 'json-stringify',
538- 'gallery-form']});
539+ 'gallery-form', 'panel']});

Subscribers

People subscribed via source and target branches