← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~thumper/launchpad/choice-widget into lp:launchpad

 

Tim Penhey has proposed merging lp:~thumper/launchpad/choice-widget into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~thumper/launchpad/choice-widget/+merge/52357

Call this a tech-debt removal branch, in that we have too much
specific javascript for simply updating an enum field.

A new widget is created to our bucket of widgets: EnumChoiceWidget.

As a proof of concept, the branch status update is changed to
use this new widget.

-- 
https://code.launchpad.net/~thumper/launchpad/choice-widget/+merge/52357
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~thumper/launchpad/choice-widget into lp:launchpad.
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
--- lib/canonical/launchpad/icing/style-3-0.css.in	2011-03-03 18:56:00 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css.in	2011-03-07 03:24:16 +0000
@@ -1618,7 +1618,7 @@
     }
 .branchstatusMERGED, .branchstatusMERGED a,
 .branchstatusABANDONED, .branchstatusABANDONED a {
-    color: #gray;
+    color: gray;
     }
 .branchstatusNEW, .branchstatusNEW a {
     color: black;

=== modified file 'lib/lp/app/browser/lazrjs.py'
--- lib/lp/app/browser/lazrjs.py	2011-02-10 01:57:55 +0000
+++ lib/lp/app/browser/lazrjs.py	2011-03-07 03:24:16 +0000
@@ -21,6 +21,7 @@
 from zope.schema.interfaces import IVocabulary
 from zope.schema.vocabulary import getVocabularyRegistry
 
+from lazr.enum import IEnumeratedType
 from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED
 from canonical.lazr.utils import get_current_browser_request
 from canonical.lazr.utils import safe_hasattr
@@ -406,3 +407,38 @@
     @property
     def json_config(self):
         return simplejson.dumps(self.config)
+
+
+class EnumChoiceWidget(EditableWidgetBase):
+    """A popup choice widget."""
+
+    __call__ = ViewPageTemplateFile('../templates/enum-choice-widget.pt')
+
+    def __init__(self, context, exported_field, header,
+                 content_box_id=None, enum=None,
+                 edit_view="+edit", edit_url=None,
+                 css_class_prefix=''):
+        super(EnumChoiceWidget, self).__init__(
+            context, exported_field, content_box_id, edit_view, edit_url)
+        self.header = header
+        value = getattr(self.context, self.attribute_name)
+        self.css_class = "value %s%s" % (css_class_prefix, value.name)
+        self.value = value.title
+        if enum is None:
+            # Get the enum from the exported field.
+            enum = exported_field.vocabulary
+        if IEnumeratedType(enum, None) is None:
+            raise ValueError('%r does not provide IEnumeratedType' % enum)
+        self.items = vocabulary_to_choice_edit_items(enum, css_class_prefix)
+
+    @property
+    def config(self):
+        return dict(
+            contentBox='#'+self.content_box_id,
+            value=self.value,
+            title=self.header,
+            items=self.items)
+
+    @property
+    def json_config(self):
+        return simplejson.dumps(self.config)

=== modified file 'lib/lp/app/javascript/choice.js'
--- lib/lp/app/javascript/choice.js	2011-02-14 00:44:44 +0000
+++ lib/lp/app/javascript/choice.js	2011-03-07 03:24:16 +0000
@@ -2,18 +2,7 @@
 
 var namespace = Y.namespace('lp.app.choice');
 
-namespace.addBinaryChoice = function(config, resource_uri, attribute) {
-
-  if (Y.UA.ie) {
-    return;
-  }
-
-  var widget = new Y.ChoiceSource(config);
-  widget.plug({
-    fn: Y.lp.client.plugins.PATCHPlugin,
-    cfg: {
-      patch: attribute,
-      resource: resource_uri}});
+function hook_up_spinner(widget) {
   // ChoiceSource makes assumptions about HTML in lazr-js
   // that don't hold true here, so we need to do our own
   // spinner icon and clear it when finished.
@@ -30,8 +19,46 @@
     icon.addClass('edit');
     icon.setStyle('bottom', '0px');
   }, widget, '_uiClearWaiting');
+}
+
+namespace.addBinaryChoice = function(config, resource_uri, attribute) {
+
+  if (Y.UA.ie) {
+    return;
+  }
+
+  var widget = new Y.ChoiceSource(config);
+  widget.plug({
+    fn: Y.lp.client.plugins.PATCHPlugin,
+    cfg: {
+      patch: attribute,
+      resource: resource_uri}});
+  hook_up_spinner(widget);
   widget.render();
 };
 
 
+namespace.addEnumChoice = function(config, resource_uri, attribute) {
+
+  var widget = new Y.ChoiceSource(config);
+  widget.plug({
+    fn: Y.lp.client.plugins.PATCHPlugin,
+    cfg: {
+      patch: attribute,
+      resource: resource_uri}});
+  hook_up_spinner(widget);
+  widget.on('save', function(e) {
+      var cb = widget.get('contentBox');
+      var value = widget.get('value');
+      Y.Array.each(config.items, function(item) {
+          if (item.value == value) {
+            cb.one('span').addClass(item.css_class);
+          } else {
+            cb.one('span').removeClass(item.css_class);
+          }
+        });
+    });
+  widget.render();
+}
+
 }, "0.1", {"requires": ["lazr.choiceedit", "lp.client.plugins"]});

=== added file 'lib/lp/app/templates/enum-choice-widget.pt'
--- lib/lp/app/templates/enum-choice-widget.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/app/templates/enum-choice-widget.pt	2011-03-07 03:24:16 +0000
@@ -0,0 +1,17 @@
+<span tal:attributes="id view/content_box_id">
+  <span tal:attributes="class view/css_class"
+        tal:content="structure view/value" />
+  <span tal:condition="view/can_write">&nbsp;
+    <a tal:attributes="href view/edit_url"
+       class="editicon sprite edit"></a>
+  </span>
+</span>
+<script tal:condition="view/can_write"
+        tal:content="structure string:
+LPS.use('lp.app.choice', function(Y) {
+    Y.lp.app.choice.addEnumChoice(
+        ${view/json_config},
+        ${view/json_resource_uri},
+        ${view/json_attribute});
+});
+"/>

=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2011-02-27 19:45:44 +0000
+++ lib/lp/code/browser/branch.py	2011-03-07 03:24:16 +0000
@@ -107,6 +107,7 @@
     )
 from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
 from lp.app.errors import NotFoundError
+from lp.app.browser.lazrjs import EnumChoiceWidget
 from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
 from lp.app.widgets.suggestion import TargetBranchWidget
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
@@ -668,17 +669,11 @@
         return list(self.context.getProductSeriesPushingTranslations())
 
     @property
-    def status_config(self):
+    def status_widget(self):
         """The config to configure the ChoiceSource JS widget."""
-        return simplejson.dumps({
-            'status_widget_items': vocabulary_to_choice_edit_items(
-                BranchLifecycleStatus,
-                css_class_prefix='branchstatus'),
-            'status_value': self.context.lifecycle_status.title,
-            'user_can_edit_status': check_permission(
-                'launchpad.Edit', self.context),
-            'branch_path': '/' + self.context.unique_name,
-            })
+        return EnumChoiceWidget(
+            self.context.branch, IBranch['lifecycle_status'],
+            header='Change status to', css_class_prefix='branchstatus')
 
 
 class BranchInProductView(BranchView):

=== removed file 'lib/lp/code/javascript/branch.status.js'
--- lib/lp/code/javascript/branch.status.js	2010-07-15 10:55:27 +0000
+++ lib/lp/code/javascript/branch.status.js	1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
-/* Copyright 2009 Canonical Ltd.  This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE).
- *
- * Code for handling the update of the branch status.
- *
- * @module lp.code.branchstatus
- * @requires node, lazr.choiceedit, lp.client.plugins
- */
-
-YUI.add('lp.code.branch.status', function(Y) {
-
-var namespace = Y.namespace('lp.code.branch.status');
-
-/*
- * Connect the branch status to the javascript events.
- */
-namespace.connect_status = function(conf) {
-
-    var status_content = Y.one('#branch-details-status-value');
-
-    if (conf.user_can_edit_status) {
-        var status_choice_edit = new Y.ChoiceSource({
-            contentBox: status_content,
-            value: conf.status_value,
-            title: 'Change status to',
-            items: conf.status_widget_items});
-        status_choice_edit.showError = function(err) {
-            Y.lp.app.errors.display_error(null, err);
-        };
-        status_choice_edit.on('save', function(e) {
-            var cb = status_choice_edit.get('contentBox');
-            Y.Array.each(conf.status_widget_items, function(item) {
-                if (item.value == status_choice_edit.get('value')) {
-                    cb.one('span').addClass(item.css_class);
-                } else {
-                    cb.one('span').removeClass(item.css_class);
-                }
-            });
-        });
-        status_choice_edit.plug({
-            fn: Y.lp.client.plugins.PATCHPlugin,
-            cfg: {
-                patch: 'lifecycle_status',
-                resource: conf.branch_path}});
-        status_choice_edit.render();
-    }
-};
-
-}, "0.1", {"requires": ["node", "lazr.choiceedit", "lp.client.plugins"]});

=== modified file 'lib/lp/code/templates/branch-index.pt'
--- lib/lp/code/templates/branch-index.pt	2011-02-23 22:32:15 +0000
+++ lib/lp/code/templates/branch-index.pt	2011-03-07 03:24:16 +0000
@@ -43,8 +43,6 @@
                     contentBox: '#portlet-subscribers'
                 });
                 subscription_portlet.render();
-
-                Y.lp.code.branch.status.connect_status(${view/status_config});
             }
             Y.lp.code.branchmergeproposal.diff.connect_diff_links();
         }, window);

=== modified file 'lib/lp/code/templates/branch-information.pt'
--- lib/lp/code/templates/branch-information.pt	2010-10-19 02:20:08 +0000
+++ lib/lp/code/templates/branch-information.pt	2011-03-07 03:24:16 +0000
@@ -22,15 +22,7 @@
 
     <dl id="status">
       <dt>Status:</dt>
-      <dd>
-        <span id="branch-details-status-value">
-        <span tal:attributes="class string:value branchstatus${context/lifecycle_status/name}"
-              tal:content="structure context/lifecycle_status/title" />&nbsp;
-        <a href="+edit-status" tal:condition="context/required:launchpad.Edit">
-          <img class="editicon" src="/@@/edit"/>
-        </a>
-        </span>
-      </dd>
+      <dd tal:content="structure view/status_widget" />
     </dl>
   </div>
 

=== modified file 'lib/lp/code/windmill/tests/test_branch_status.py'
--- lib/lp/code/windmill/tests/test_branch_status.py	2011-02-10 04:00:00 +0000
+++ lib/lp/code/windmill/tests/test_branch_status.py	2011-03-07 03:24:16 +0000
@@ -6,19 +6,15 @@
 __metaclass__ = type
 __all__ = []
 
-import unittest
-
 import transaction
-import windmill
-
+
+from storm.store import Store
+
+from lp.code.enums import BranchLifecycleStatus
+from lp.code.model.branch import Branch
 from lp.code.windmill.testing import CodeWindmillLayer
 from lp.testing import WindmillTestCase
-from lp.testing.windmill.constants import (
-    FOR_ELEMENT,
-    PAGE_LOAD,
-    SLEEP,
-    )
-from lp.testing.windmill.lpuser import login_person
+from lp.testing.windmill.constants import FOR_ELEMENT
 
 
 class TestBranchStatus(WindmillTestCase):
@@ -37,37 +33,23 @@
 
         client = self.client
 
-        start_url = (
-            windmill.settings['TEST_URL'] + branch.unique_name)
-        client.open(url=start_url)
-        client.waits.forPageLoad(timeout=PAGE_LOAD)
-        login_person(eric, "eric@xxxxxxxxxxx", "test", client)
-
+        client, start_url = self.getClientFor(branch, user=eric)
         # Click on the element containing the branch status.
-        client.waits.forElement(
-            id=u'branch-details-status-value', timeout=PAGE_LOAD)
-        client.click(id=u'branch-details-status-value')
-        client.waits.forElement(
-            xpath=u'//div[contains(@class, "yui3-ichoicelist-content")]')
-
-        # Change the status to experimental.
+        client.click(id=u'edit-lifecycle_status')
+        client.waits.forElement(
+            classname=u'yui3-ichoicelist-content', timeout=FOR_ELEMENT)
         client.click(link=u'Experimental')
-        client.waits.sleep(milliseconds=SLEEP)
-
-        client.asserts.assertText(
-            xpath=u'//span[@id="branch-details-status-value"]/span',
-            validator=u'Experimental')
-
-        # Reload the page and make sure the change sticks.
-        client.open(url=start_url)
-        client.waits.forPageLoad(timeout=PAGE_LOAD)
         client.waits.forElement(
-            xpath=u'//span[@id="branch-details-status-value"]/span',
+            jquery=u'("div#edit-lifecycle_status a.editicon.sprite.edit")',
             timeout=FOR_ELEMENT)
-        client.asserts.assertText(
-            xpath=u'//span[@id="branch-details-status-value"]/span',
-            validator=u'Experimental')
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)
+        client.asserts.assertTextIn(
+            id=u'edit-lifecycle_status', validator=u'Experimental')
+        client.asserts.assertNode(
+            jquery=u'("div#edit-lifecycle_status span.value.branchstatusEXPERIMENTAL")')
+
+        transaction.commit()
+        freshly_fetched_branch = Store.of(branch).find(
+            Branch, Branch.id == branch.id).one()
+        self.assertEqual(
+            BranchLifecycleStatus.EXPERIMENTAL,
+            freshly_fetched_branch.lifecycle_status)