launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03394
[Merge] lp:~benji/launchpad/fix-help-link-2 into lp:launchpad/db-devel
Benji York has proposed merging lp:~benji/launchpad/fix-help-link-2 into lp:launchpad/db-devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~benji/launchpad/fix-help-link-2/+merge/58521
This branch fixes a small bug: the help link next to the "Stop my emails
from this subscription" link wasn't appearing on Chrome. I'm still not
sure why not, but the work-around was simple enough.
--
https://code.launchpad.net/~benji/launchpad/fix-help-link-2/+merge/58521
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~benji/launchpad/fix-help-link-2 into lp:launchpad/db-devel.
=== modified file 'lib/canonical/configure.zcml'
--- lib/canonical/configure.zcml 2011-04-19 14:13:21 +0000
+++ lib/canonical/configure.zcml 2011-04-20 15:32:20 +0000
@@ -134,4 +134,7 @@
<include package="canonical.launchpad" />
<include package="canonical.lazr" />
+ <include zcml:condition="installed canonical.shipit"
+ package="canonical.shipit" />
+
</configure>
=== modified file 'lib/canonical/launchpad/webapp/error.py'
--- lib/canonical/launchpad/webapp/error.py 2011-04-19 04:17:54 +0000
+++ lib/canonical/launchpad/webapp/error.py 2011-04-20 15:32:20 +0000
@@ -241,6 +241,10 @@
# is really just a guess and I don't think any clients actually
# pay attention to it - it is just a hint.
request.response.setHeader('Retry-After', 900)
+ # Reset the timeout timer, so that we can issue db queries when
+ # rendering the page.
+ clear_request_started()
+ set_request_started()
class InvalidBatchSizeView(SystemErrorView):
=== added file 'lib/canonical/launchpad/webapp/tests/test_request_expire_render.py'
--- lib/canonical/launchpad/webapp/tests/test_request_expire_render.py 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/webapp/tests/test_request_expire_render.py 2011-04-20 15:32:20 +0000
@@ -0,0 +1,27 @@
+# Copyright 2009 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test for rendering the time out OOPS page."""
+
+__metaclass__ = type
+
+__all__ = [
+ 'test_suite',
+ ]
+
+
+from canonical.launchpad.testing.systemdocs import (
+ LayeredDocFileSuite,
+ setUp,
+ tearDown,
+ )
+from canonical.testing.layers import LaunchpadFunctionalLayer
+
+
+def test_suite():
+ suite = LayeredDocFileSuite(
+ 'test_request_expire_render.txt',
+ layer=LaunchpadFunctionalLayer, setUp=setUp, tearDown=tearDown,
+ )
+ return suite
+
=== added file 'lib/canonical/launchpad/webapp/tests/test_request_expire_render.txt'
--- lib/canonical/launchpad/webapp/tests/test_request_expire_render.txt 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/webapp/tests/test_request_expire_render.txt 2011-04-20 15:32:20 +0000
@@ -0,0 +1,55 @@
+= Rendering timeout exceptions =
+
+When a page takes too long to render, a timeout exception is raised. A
+timeout exception should provide IRequestExpired.
+
+ >>> from zope.interface import implements
+ >>> from canonical.database.interfaces import IRequestExpired
+ >>> class TimeoutException:
+ ... implements(IRequestExpired)
+
+After a timeout has happened, we can no longer do any more db queries,
+since get_request_remaining_seconds will raise. However, if the user is logged
+in, a query will be issued to render the "Logged in as No Privileges Person".
+
+ >>> from canonical.config import config
+ >>> from textwrap import dedent
+ >>> test_data = dedent("""
+ ... [database]
+ ... db_statement_timeout: 10000
+ ... """)
+ >>> config.push('test_data', test_data)
+
+ >>> from time import time
+ >>> from canonical.launchpad.webapp.adapter import (
+ ... get_request_remaining_seconds, set_request_started)
+ >>> login('no-priv@xxxxxxxxxxxxx')
+ >>> too_early = time() - (config.database.db_statement_timeout / 1000 + 1)
+ >>> set_request_started(too_early)
+ >>> get_request_remaining_seconds()
+ Traceback (most recent call last):
+ ...
+ RequestExpired: request expired.
+
+Before the OOPS page is rendered, the timeout is reset, so that we can
+issue the DB query and render the page, showing the OOPS id to the user.
+
+ >>> from canonical.launchpad.webapp.interaction import (
+ ... get_current_principal)
+ >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+ >>> request = LaunchpadTestRequest(environ={'PATH_INFO': '/'})
+ >>> request.oopsid = 'OOPS_ID_MARKER'
+ >>> request.setPrincipal(get_current_principal())
+
+ >>> from zope.component import getMultiAdapter
+ >>> timeout_exception = TimeoutException()
+ >>> exception_view = getMultiAdapter(
+ ... (timeout_exception, request), name="index.html")
+ >>> get_request_remaining_seconds() > 0
+ True
+ >>> print exception_view()
+ <...OOPS_ID_MARKER...
+
+ >>> test_config_data = config.pop('test_data')
+ >>> from canonical.launchpad.webapp.adapter import clear_request_started
+ >>> clear_request_started()
=== added symlink 'lib/canonical/shipit'
=== target is u'../../sourcecode/shipit'
=== modified file 'lib/lp/app/javascript/picker.js'
--- lib/lp/app/javascript/picker.js 2011-04-20 06:29:11 +0000
+++ lib/lp/app/javascript/picker.js 2011-04-20 15:32:20 +0000
@@ -243,8 +243,8 @@
'<div class="transparent important-notice-popup">',
'<div class="validation-content-placeholder"></div>',
'<div class="extra-form-buttons">',
- '<button class="yes_button" type="button"></button>',
- '<button class="no_button" type="button"></button>',
+ '<button class="yes_button" type="button"/>',
+ '<button class="no_button" type="button"/>',
'</div>',
'</div>',
'</div>'].join(''));
@@ -358,13 +358,9 @@
});
var picker = new Y.lazr.Picker(new_config);
- // We don't want the Y.lazr.Picker default save to fire since this hides
- // the form. We want to do this ourselves after any validation has had a
- // chance to be performed.
- picker.publish('save', { defaultFn: function(){} } );
-
picker.subscribe('save', function (e) {
Y.log('Got save event.');
+ e.preventDefault();
var picker_result = e.details[Y.lazr.Picker.SAVE_RESULT];
var do_save = function() {
if (Y.Lang.isFunction(config.save)) {
=== renamed file 'lib/lp/app/javascript/tests/test_picker.html' => 'lib/lp/app/javascript/tests/picker.html'
--- lib/lp/app/javascript/tests/test_picker.html 2011-04-19 11:27:26 +0000
+++ lib/lp/app/javascript/tests/picker.html 2011-04-20 15:32:20 +0000
@@ -24,7 +24,7 @@
<script type="text/javascript" src="../picker.js"></script>
<!-- The test suite -->
- <script type="text/javascript" src="test_picker.js"></script>
+ <script type="text/javascript" src="picker.js"></script>
</head>
<body class="yui3-skin-sam">
<div class="yui3-widget yui3-activator yui3-activator-focused">
=== renamed file 'lib/lp/app/javascript/tests/test_picker.js' => 'lib/lp/app/javascript/tests/picker.js'
--- lib/lp/app/javascript/tests/test_picker.js 2011-04-15 08:16:32 +0000
+++ lib/lp/app/javascript/tests/picker.js 2011-04-20 15:32:20 +0000
@@ -118,29 +118,6 @@
Assert.isTrue(save_flag.event_has_fired, "save event wasn't fired.");
},
- test_TextFieldPickerPlugin_selected_item_is_saved: function () {
- // Test that the picker saves the selected value to its associated
- // textfield if one is defined.
- var search_input = Y.Node.create(
- '<input id="field.initval" value="foo" />');
- node = Y.one(document.body).appendChild(search_input);
- this.create_picker();
- this.picker.plug(Y.lazr.TextFieldPickerPlugin,
- {input_element: '[id="field.initval"]'});
- this.picker.set('results', this.vocabulary);
- this.picker.render();
- var got_focus = false;
- search_input.on('focus', function(e) {
- got_focus = true;
- });
- simulate(
- this.picker.get('boundingBox').one('.yui3-picker-results'),
- 'li:nth-child(1)', 'click');
- Assert.areEqual(
- 'fred', Y.one('[id="field.initval"]').get("value"));
- Assert.isTrue(got_focus, "focus didn't go to the search input.");
- },
-
test_confirmation_yes: function() {
// Test that the picker saves the selected value if the user answers
// "Yes" to a confirmation request.
=== modified file 'lib/lp/code/interfaces/codehosting.py'
--- lib/lp/code/interfaces/codehosting.py 2011-04-18 01:37:03 +0000
+++ lib/lp/code/interfaces/codehosting.py 2011-04-20 15:32:20 +0000
@@ -8,7 +8,6 @@
__metaclass__ = type
__all__ = [
'BRANCH_ALIAS_PREFIX',
- 'branch_id_alias',
'BRANCH_ID_ALIAS_PREFIX',
'BRANCH_TRANSPORT',
'compose_public_url',
@@ -61,11 +60,6 @@
BRANCH_ID_ALIAS_PREFIX = '+branch-id'
-def branch_id_alias(branch):
- """Return the path using the branch id alias."""
- return '/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id)
-
-
# The scheme types that are supported for codehosting.
SUPPORTED_SCHEMES = 'bzr+ssh', 'http'
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2011-04-18 01:37:03 +0000
+++ lib/lp/code/model/tests/test_branch.py 2011-04-20 15:32:20 +0000
@@ -79,7 +79,7 @@
)
from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
from lp.code.interfaces.branchrevision import IBranchRevision
-from lp.code.interfaces.codehosting import branch_id_alias
+from lp.code.interfaces.codehosting import BRANCH_ID_ALIAS_PREFIX
from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
from lp.code.interfaces.seriessourcepackagebranch import (
IFindOfficialBranchLinks,
@@ -187,7 +187,8 @@
branch = self.factory.makeAnyBranch()
stacked_on = self.factory.makeAnyBranch()
login_person(branch.owner)
- stacked_on_location = branch_id_alias(stacked_on)
+ stacked_on_location = '/%s/%s' % (
+ BRANCH_ID_ALIAS_PREFIX, stacked_on.id)
branch.branchChanged(stacked_on_location, '', *self.arbitrary_formats)
self.assertEqual(stacked_on, branch.stacked_on)
=== modified file 'lib/lp/code/model/tests/test_branchlookup.py'
--- lib/lp/code/model/tests/test_branchlookup.py 2011-04-18 01:37:03 +0000
+++ lib/lp/code/model/tests/test_branchlookup.py 2011-04-20 15:32:20 +0000
@@ -25,10 +25,7 @@
ILinkedBranchTraverser,
)
from lp.code.interfaces.branchnamespace import get_branch_namespace
-from lp.code.interfaces.codehosting import (
- branch_id_alias,
- BRANCH_ID_ALIAS_PREFIX,
- )
+from lp.code.interfaces.codehosting import BRANCH_ID_ALIAS_PREFIX
from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
from lp.registry.errors import (
NoSuchDistroSeries,
@@ -140,28 +137,28 @@
owner = self.factory.makePerson()
branch = self.factory.makeAnyBranch(owner=owner, private=True)
with person_logged_in(owner):
- path = branch_id_alias(branch)
+ path = '/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id)
result = self.branch_set.getIdAndTrailingPath(path)
self.assertEqual((None, None), result)
def test_branch_id_alias_public(self):
# Public branches can be accessed.
branch = self.factory.makeAnyBranch()
- path = branch_id_alias(branch)
+ path = '/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id)
result = self.branch_set.getIdAndTrailingPath(path)
self.assertEqual((branch.id, ''), result)
def test_branch_id_alias_public_slash(self):
# A trailing slash is returned as the extra path.
branch = self.factory.makeAnyBranch()
- path = '%s/' % branch_id_alias(branch)
+ path = '/%s/%s/' % (BRANCH_ID_ALIAS_PREFIX, branch.id)
result = self.branch_set.getIdAndTrailingPath(path)
self.assertEqual((branch.id, '/'), result)
def test_branch_id_alias_public_with_path(self):
# All the path after the number is returned as the trailing path.
branch = self.factory.makeAnyBranch()
- path = '%s/foo' % branch_id_alias(branch)
+ path = '/%s/%s/foo' % (BRANCH_ID_ALIAS_PREFIX, branch.id)
result = self.branch_set.getIdAndTrailingPath(path)
self.assertEqual((branch.id, '/foo'), result)
=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
--- lib/lp/code/xmlrpc/codehosting.py 2011-04-18 01:37:03 +0000
+++ lib/lp/code/xmlrpc/codehosting.py 2011-04-20 15:32:20 +0000
@@ -59,7 +59,6 @@
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codehosting import (
BRANCH_ALIAS_PREFIX,
- branch_id_alias,
BRANCH_ID_ALIAS_PREFIX,
BRANCH_TRANSPORT,
CONTROL_TRANSPORT,
@@ -321,12 +320,12 @@
if default_branch is None:
return
try:
- path = branch_id_alias(default_branch)
+ unique_name = default_branch.unique_name
except Unauthorized:
return
return (
CONTROL_TRANSPORT,
- {'default_stack_on': escape(path)},
+ {'default_stack_on': escape('/' + unique_name)},
trailing_path)
def _translateBranchIdAlias(self, requester, path):
=== modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py'
--- lib/lp/code/xmlrpc/tests/test_codehosting.py 2011-04-18 01:37:03 +0000
+++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2011-04-20 15:32:20 +0000
@@ -41,7 +41,6 @@
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codehosting import (
BRANCH_ALIAS_PREFIX,
- branch_id_alias,
BRANCH_ID_ALIAS_PREFIX,
BRANCH_TRANSPORT,
CONTROL_TRANSPORT,
@@ -994,8 +993,8 @@
self.assertNotFound(requester, '/%s/.bzr' % BRANCH_ALIAS_PREFIX)
def test_translatePath_branch_id_alias_bzrdir_content(self):
- # translatePath('/+branch-id/.bzr/.*') *must* return not found,
- # otherwise bzr will look for it and we don't have a global bzr dir.
+ # translatePath('/+branch-id/.bzr/.*') *must* return not found, otherwise
+ # bzr will look for it and we don't have a global bzr dir.
requester = self.factory.makePerson()
self.assertNotFound(
requester, '/%s/.bzr/branch-format' % BRANCH_ID_ALIAS_PREFIX)
@@ -1010,7 +1009,7 @@
# Make sure the trailing path is returned.
requester = self.factory.makePerson()
branch = removeSecurityProxy(self.factory.makeAnyBranch())
- path = escape(u'%s/foo/bar' % branch_id_alias(branch))
+ path = escape(u'/%s/%s/foo/bar' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
translation = self.codehosting_api.translatePath(requester.id, path)
self.assertEqual(
(BRANCH_TRANSPORT, {'id': branch.id, 'writable': False}, 'foo/bar'),
@@ -1022,7 +1021,7 @@
branch = removeSecurityProxy(
self.factory.makeAnyBranch(
branch_type=BranchType.HOSTED, owner=requester))
- path = escape(branch_id_alias(branch))
+ path = escape(u'/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
translation = self.codehosting_api.translatePath(requester.id, path)
self.assertEqual(
(BRANCH_TRANSPORT, {'id': branch.id, 'writable': False}, ''),
@@ -1035,7 +1034,7 @@
branch = removeSecurityProxy(
self.factory.makeAnyBranch(
branch_type=BranchType.HOSTED, private=True, owner=requester))
- path = escape(branch_id_alias(branch))
+ path = escape(u'/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
translation = self.codehosting_api.translatePath(requester.id, path)
self.assertEqual(
(BRANCH_TRANSPORT, {'id': branch.id, 'writable': False}, ''),
@@ -1047,16 +1046,17 @@
branch = removeSecurityProxy(
self.factory.makeAnyBranch(
branch_type=BranchType.HOSTED, private=True))
- path = escape(branch_id_alias(branch))
+ path = escape(u'/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
self.assertPermissionDenied(requester, path)
def assertTranslationIsControlDirectory(self, translation,
default_stacked_on,
trailing_path):
"""Assert that 'translation' points to the right control transport."""
+ unique_name = escape(u'/' + default_stacked_on)
expected_translation = (
CONTROL_TRANSPORT,
- {'default_stack_on': escape(default_stacked_on)}, trailing_path)
+ {'default_stack_on': unique_name}, trailing_path)
self.assertEqual(expected_translation, translation)
def test_translatePath_control_directory(self):
@@ -1067,7 +1067,7 @@
login(ANONYMOUS)
self.assertTranslationIsControlDirectory(
translation,
- default_stacked_on=branch_id_alias(branch),
+ default_stacked_on=branch.unique_name,
trailing_path='.bzr')
def test_translatePath_control_directory_no_stacked_set(self):
@@ -1093,7 +1093,7 @@
login(ANONYMOUS)
self.assertTranslationIsControlDirectory(
translation,
- default_stacked_on=branch_id_alias(branch),
+ default_stacked_on=branch.unique_name,
trailing_path='.bzr')
def test_translatePath_control_directory_other_owner(self):
@@ -1105,7 +1105,7 @@
login(ANONYMOUS)
self.assertTranslationIsControlDirectory(
translation,
- default_stacked_on=branch_id_alias(branch),
+ default_stacked_on=branch.unique_name,
trailing_path='.bzr')
def test_translatePath_control_directory_package_no_focus(self):
@@ -1131,7 +1131,7 @@
login(ANONYMOUS)
self.assertTranslationIsControlDirectory(
translation,
- default_stacked_on=branch_id_alias(branch),
+ default_stacked_on=branch.unique_name,
trailing_path='.bzr')
=== modified file 'lib/lp/codehosting/inmemory.py'
--- lib/lp/codehosting/inmemory.py 2011-04-18 01:37:03 +0000
+++ lib/lp/codehosting/inmemory.py 2011-04-20 15:32:20 +0000
@@ -37,7 +37,6 @@
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codehosting import (
BRANCH_ALIAS_PREFIX,
- branch_id_alias,
BRANCH_ID_ALIAS_PREFIX,
BRANCH_TRANSPORT,
CONTROL_TRANSPORT,
@@ -803,10 +802,9 @@
return
if not self._canRead(requester, default_branch):
return
- path = branch_id_alias(default_branch)
return (
CONTROL_TRANSPORT,
- {'default_stack_on': escape(path)},
+ {'default_stack_on': escape('/' + default_branch.unique_name)},
trailing_path)
def _serializeBranch(self, requester_id, branch, trailing_path,
=== modified file 'lib/lp/codehosting/tests/test_rewrite.py'
--- lib/lp/codehosting/tests/test_rewrite.py 2011-04-18 01:37:03 +0000
+++ lib/lp/codehosting/tests/test_rewrite.py 2011-04-20 15:32:20 +0000
@@ -16,7 +16,7 @@
from canonical.config import config
from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.code.interfaces.codehosting import branch_id_alias
+from lp.code.interfaces.codehosting import BRANCH_ID_ALIAS_PREFIX
from lp.codehosting.rewrite import BranchRewriter
from lp.codehosting.vfs import branch_id_to_path
from lp.services.log.logger import BufferLogger
@@ -102,8 +102,8 @@
self.factory.makePackageBranch(private=False)]
transaction.commit()
output = [
- rewriter.rewriteLine(
- "%s/.bzr/README" % branch_id_alias(branch))
+ rewriter.rewriteLine("/%s/%s/.bzr/README" % (
+ BRANCH_ID_ALIAS_PREFIX, branch.id))
for branch in branches]
expected = [
'file:///var/tmp/bazaar.launchpad.dev/mirrors/%s/.bzr/README'
@@ -116,7 +116,8 @@
# 'NULL'. This is translated by apache to a 404.
rewriter = self.makeRewriter()
branch = self.factory.makeAnyBranch(private=True)
- path = branch_id_alias(removeSecurityProxy(branch))
+ path = '/%s/%s' % (
+ BRANCH_ID_ALIAS_PREFIX, removeSecurityProxy(branch).id)
transaction.commit()
output = [
rewriter.rewriteLine("%s/changes" % path),
@@ -129,7 +130,7 @@
rewriter = self.makeRewriter()
branch = self.factory.makeAnyBranch()
transaction.commit()
- path = "%s/.bzr/README" % branch_id_alias(branch)
+ path = "/%s/%s/.bzr/README" % (BRANCH_ID_ALIAS_PREFIX, branch.id)
rewriter.rewriteLine(path)
rewriter.rewriteLine(path)
logging_output_lines = self.getLoggerOutput(
=== modified file 'lib/lp/codehosting/vfs/tests/test_branchfs.py'
--- lib/lp/codehosting/vfs/tests/test_branchfs.py 2011-04-18 23:05:14 +0000
+++ lib/lp/codehosting/vfs/tests/test_branchfs.py 2011-04-20 15:32:20 +0000
@@ -55,7 +55,6 @@
)
from lp.code.enums import BranchType
from lp.code.interfaces.codehosting import (
- branch_id_alias,
BRANCH_TRANSPORT,
CONTROL_TRANSPORT,
)
@@ -267,7 +266,7 @@
def check_control_file((transport, path)):
self.assertEqual(
- 'default_stack_on = %s\n' % branch_id_alias(branch),
+ 'default_stack_on = /%s\n' % branch.unique_name,
transport.get_bytes(path))
return deferred.addCallback(check_control_file)
=== modified file 'lib/lp/codehosting/vfs/tests/test_filesystem.py'
--- lib/lp/codehosting/vfs/tests/test_filesystem.py 2011-04-18 23:05:14 +0000
+++ lib/lp/codehosting/vfs/tests/test_filesystem.py 2011-04-20 15:32:20 +0000
@@ -16,7 +16,6 @@
from bzrlib.urlutils import escape
from lp.code.interfaces.branchtarget import IBranchTarget
-from lp.code.interfaces.codehosting import branch_id_alias
from lp.codehosting.inmemory import (
InMemoryFrontend,
XMLRPCWrapper,
@@ -140,9 +139,9 @@
control_file = transport.get_bytes(
'~%s/%s/.bzr/control.conf'
% (self.requester.name, product.name))
- stacked_on = IBranchTarget(product).default_stacked_on_branch
self.assertEqual(
- 'default_stack_on = %s' % branch_id_alias(stacked_on),
+ 'default_stack_on = /%s'
+ % IBranchTarget(product).default_stacked_on_branch.unique_name,
control_file.strip())
def test_can_open_product_control_dir(self):
=== modified file 'lib/lp/registry/javascript/structural-subscription.js'
--- lib/lp/registry/javascript/structural-subscription.js 2011-04-19 07:18:42 +0000
+++ lib/lp/registry/javascript/structural-subscription.js 2011-04-20 15:32:20 +0000
@@ -1204,8 +1204,9 @@
var mute_link = node.one('a.mute-subscription');
mute_link.on('click', make_mute_handler(filter_info, node));
}
- if (!filter_info.subscriber_is_team ||
- filter_info.user_is_team_admin) {
+ var can_edit = (!filter_info.subscriber_is_team ||
+ filter_info.user_is_team_admin);
+ if (can_edit) {
var edit_link = node.one('a.edit-subscription');
var edit_handler = make_edit_handler(
subscription, filter_info, filter_id, config);
@@ -1276,9 +1277,6 @@
'do not receive emails from this subscription.</em>'));
}
- var can_edit = (!filter_info.subscriber_is_team ||
- filter_info.user_is_team_admin);
-
var control = filter_node.appendChild(
Y.Node.create('<span style="float: right"></span>'));
@@ -1291,6 +1289,9 @@
' href="/+help/structural-subscription-mute.html">'+
' <span class="invisible-link">Delivery help</span>'+
'</a>'));
+ // For some reason the help link will not appear in Chrome unless
+ // there is a non-empty element immediately after the help node.
+ control.append(Y.Node.create('<span> </span>'));
// We store a reference to the timeout that will hide the help link so
// we can cancel it if needed.
var hide_help_timeout;
@@ -1309,6 +1310,9 @@
link.on('hover', show_help, hide_help);
help.on('hover', show_help, hide_help);
}
+
+ var can_edit = (!filter_info.subscriber_is_team ||
+ filter_info.user_is_team_admin);
if (can_edit) {
// User can edit the subscription.
control.append(Y.Node.create(
=== modified file 'lib/lp/registry/tests/test_distroseriesdifference.py'
--- lib/lp/registry/tests/test_distroseriesdifference.py 2011-04-19 12:00:38 +0000
+++ lib/lp/registry/tests/test_distroseriesdifference.py 2011-04-20 15:32:20 +0000
@@ -163,52 +163,6 @@
DistroSeriesDifferenceStatus.RESOLVED,
ds_diff.status)
- def test_update_nulls_diffs_for_resolved(self):
- # Resolved differences should null out the package_diff and
- # parent_package_diff fields so the libraryfilealias gets
- # considered for GC later.
- derived_changelog = self.factory.makeChangelog(
- versions=['1.0', '1.2'])
- parent_changelog = self.factory.makeChangelog(
- versions=['1.0', '1.3'])
- transaction.commit() # Yay, librarian.
- ds_diff = self.factory.makeDistroSeriesDifference(versions={
- 'derived': '1.2',
- 'parent': '1.3',
- 'base': '1.0',
- },
- changelogs={
- 'derived': derived_changelog,
- 'parent': parent_changelog,
- })
- person = self.factory.makePerson()
- with person_logged_in(person):
- ds_diff.requestPackageDiffs(person)
- # The pre-test state is that there are diffs present:
- self.assertIsNot(None, ds_diff.package_diff)
- self.assertIsNot(None, ds_diff.parent_package_diff)
-
- # Resolve the DSD by making the same package version published
- # in parent and derived.
- new_derived_pub = self.factory.makeSourcePackagePublishingHistory(
- sourcepackagename=ds_diff.source_package_name,
- distroseries=ds_diff.derived_series,
- status=PackagePublishingStatus.PENDING,
- version='1.4')
- new_parent_pub = self.factory.makeSourcePackagePublishingHistory(
- sourcepackagename=ds_diff.source_package_name,
- distroseries=ds_diff.derived_series.parent_series,
- status=PackagePublishingStatus.PENDING,
- version='1.4')
-
- # Packagediffs should be gone now.
- was_updated = ds_diff.update()
- self.assertTrue(was_updated)
- self.assertEqual(
- ds_diff.status, DistroSeriesDifferenceStatus.RESOLVED)
- self.assertIs(None, ds_diff.package_diff)
- self.assertIs(None, ds_diff.parent_package_diff)
-
def test_update_re_opens_difference(self):
# The status of a resolved difference will updated with new
# uploads.
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2011-04-20 01:57:24 +0000
+++ lib/lp/registry/tests/test_person.py 2011-04-20 15:32:20 +0000
@@ -67,7 +67,6 @@
Person,
)
from lp.services.openid.model.openididentifier import OpenIdIdentifier
-from lp.services.propertycache import clear_property_cache
from lp.soyuz.enums import (
ArchivePurpose,
ArchiveStatus,
@@ -1303,7 +1302,7 @@
class TestGetRecipients(TestCaseWithFactory):
"""Tests for get_recipients"""
- layer = DatabaseFunctionalLayer
+ layer = LaunchpadFunctionalLayer
def setUp(self):
super(TestGetRecipients, self).setUp()
@@ -1327,19 +1326,6 @@
recipients = get_recipients(super_team)
self.assertEqual(set([team]), set(recipients))
- def test_get_recipients_team_with_unvalidated_address(self):
- """Ensure get_recipients handles teams with non-preferred addresses.
-
- If there is no preferred address but one or more non-preferred ones,
- email should still be sent to the members.
- """
- owner = self.factory.makePerson(email='foo@xxxxxxx')
- team = self.factory.makeTeam(owner, email='team@xxxxxxx')
- self.assertContentEqual([team], get_recipients(team))
- team.preferredemail.status = EmailAddressStatus.NEW
- clear_property_cache(team)
- self.assertContentEqual([owner], get_recipients(team))
-
def makePersonWithNoPreferredEmail(self, **kwargs):
kwargs['email_address_status'] = EmailAddressStatus.NEW
return self.factory.makePerson(**kwargs)
=== removed file 'lib/lp/services/timeline/nestingtimedaction.py'
--- lib/lp/services/timeline/nestingtimedaction.py 2011-04-20 05:05:06 +0000
+++ lib/lp/services/timeline/nestingtimedaction.py 1970-01-01 00:00:00 +0000
@@ -1,35 +0,0 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Time an action which calls other timed actions."""
-
-
-__all__ = ['NestingTimedAction']
-
-__metaclass__ = type
-
-
-import datetime
-
-from timedaction import TimedAction
-
-
-class NestingTimedAction(TimedAction):
- """A variation of TimedAction which creates a nested environment.
-
- This is done by recording two 0 length timed actions in the timeline:
- one at the start of the action and one at the end, with -start and
- -stop appended to their categories.
-
- See `TimedAction` for more information.
- """
-
- def _init(self):
- self.duration = datetime.timedelta()
- self._category = self.category
- self.category = self._category + '-start'
-
- def finish(self):
- """Mark the TimedAction as finished."""
- end = self.timeline.start(self._category + '-stop', self.detail)
- end.duration = datetime.timedelta()
=== removed file 'lib/lp/services/timeline/tests/test_nestingtimedaction.py'
--- lib/lp/services/timeline/tests/test_nestingtimedaction.py 2011-04-20 05:05:06 +0000
+++ lib/lp/services/timeline/tests/test_nestingtimedaction.py 1970-01-01 00:00:00 +0000
@@ -1,27 +0,0 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests of the TimedAction class."""
-
-__metaclass__ = type
-
-import datetime
-
-import testtools
-
-from lp.services.timeline.nestingtimedaction import NestingTimedAction
-from lp.services.timeline.timeline import Timeline
-
-
-class TestNestingTimedAction(testtools.TestCase):
-
- def test_finish_adds_action(self):
- timeline = Timeline()
- action = NestingTimedAction("Sending mail", None, timeline)
- action.finish()
- self.assertEqual(1, len(timeline.actions))
- self.assertEqual(datetime.timedelta(), timeline.actions[-1].duration)
-
- def test__init__sets_duration(self):
- action = NestingTimedAction("Sending mail", None)
- self.assertEqual(datetime.timedelta(), action.duration)
=== modified file 'lib/lp/services/timeline/tests/test_timeline.py'
--- lib/lp/services/timeline/tests/test_timeline.py 2011-04-20 04:06:53 +0000
+++ lib/lp/services/timeline/tests/test_timeline.py 2011-04-20 15:32:20 +0000
@@ -37,69 +37,6 @@
self.assertRaises(OverlappingActionError, timeline.start,
"Sending mail", "Noone")
- def test_nested_start_permitted(self):
- # When explicitly requested a nested start can be done
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- child = timeline.start("SQL Callback", "SELECT...")
-
- def test_nested_start_is_not_transitive(self):
- # nesting is explicit at each level - not inherited.
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- child = timeline.start("SQL Callback", "SELECT...")
- self.assertRaises(OverlappingActionError, timeline.start,
- "Sending mail", "Noone")
-
- def test_multiple_nested_children_permitted(self):
- # nesting is not reset by each action that is added.
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- child = timeline.start("SQL Callback", "SELECT...")
- child.finish()
- child = timeline.start("SQL Callback", "SELECT...")
-
- def test_multiple_starts_after_nested_group_prevented(self):
- # nesting stops being permitted when the nesting action is finished.
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- action.finish()
- child = timeline.start("SQL Callback", "SELECT...")
- self.assertRaises(OverlappingActionError, timeline.start,
- "Sending mail", "Noone")
-
- def test_nesting_within_nesting_permitted(self):
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- middle = timeline.start("Calling otherlibrary", "", allow_nested=True)
- child = timeline.start("SQL Callback", "SELECT...")
-
- def test_finishing_nested_within_nested_leaves_outer_nested_nesting(self):
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- middle = timeline.start("Calling otherlibrary", "", allow_nested=True)
- middle.finish()
- child = timeline.start("SQL Callback", "SELECT...")
-
- def test_nested_actions_recorded_as_two_zero_length_actions(self):
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- child = timeline.start("SQL Callback", "SELECT...")
- child.finish()
- action.finish()
- self.assertEqual(3, len(timeline.actions))
- self.assertEqual(datetime.timedelta(), timeline.actions[0].duration)
- self.assertEqual(datetime.timedelta(), timeline.actions[2].duration)
-
- def test_nested_category_labels(self):
- # To identify start/stop pairs '-start' and '-stop' are put onto the
- # category of nested actions:
- timeline = Timeline()
- action = timeline.start("Calling openid", "hostname", allow_nested=True)
- action.finish()
- self.assertEqual('Calling openid-start', timeline.actions[0].category)
- self.assertEqual('Calling openid-stop', timeline.actions[1].category)
-
def test_start_after_finish_works(self):
timeline = Timeline()
action = timeline.start("Sending mail", "Noone")
=== modified file 'lib/lp/services/timeline/timedaction.py'
--- lib/lp/services/timeline/timedaction.py 2011-04-20 04:06:53 +0000
+++ lib/lp/services/timeline/timedaction.py 2011-04-20 15:32:20 +0000
@@ -42,15 +42,9 @@
self.category = category
self.detail = detail
self.timeline = timeline
- self._init()
-
- def _init(self):
- # hook for child classes.
- pass
def __repr__(self):
- return "<%s %s[%s]>" % (self.__class__, self.category,
- self.detail[:20])
+ return "<TimedAction %s[%s]>" % (self.category, self.detail[:20])
def logTuple(self):
"""Return a 4-tuple suitable for errorlog's use."""
=== modified file 'lib/lp/services/timeline/timeline.py'
--- lib/lp/services/timeline/timeline.py 2011-04-20 04:06:53 +0000
+++ lib/lp/services/timeline/timeline.py 2011-04-20 15:32:20 +0000
@@ -12,25 +12,19 @@
from pytz import utc as UTC
from timedaction import TimedAction
-from nestingtimedaction import NestingTimedAction
class OverlappingActionError(Exception):
"""A new action was attempted without finishing the prior one."""
- # To make analysis easy we do not permit overlapping actions by default:
- # each action that is being timed and accrued must complete before the next
+ # To make analysis easy we do not permit overlapping actions: each
+ # action that is being timed and accrued must complete before the next
# is started. This means, for instance, that sending mail cannot do SQL
# queries, as both are timed and accrued. OTOH it makes analysis and
# serialisation of timelines simpler, and for the current use cases in
# Launchpad this is sufficient. This constraint should not be considered
# sacrosanct - if, in future, we desire timelines with overlapping actions,
# as long as the OOPS analysis code is extended to generate sensible
- # reports in those situations, this can be changed. In the interim, actions
- # can be explicitly setup to permit nesting by passing allow_nested=True
- # which will cause the action to be recorded with 0 duration and a -start
- # and -stop suffix added to its category. This is potentially lossy but
- # good enough to get nested metrics and can be iterated on in the future to
- # do an actual stacked/tree model of actions - if needed.
+ # reports in those situations, this can be changed.
class Timeline:
@@ -54,20 +48,14 @@
self.actions = actions
self.baseline = datetime.datetime.now(UTC)
- def start(self, category, detail, allow_nested=False):
+ def start(self, category, detail):
"""Create a new TimedAction at the end of the timeline.
:param category: the category for the action.
:param detail: The detail for the action.
- :param allow_nested: If True treat this action as a nested action -
- record it twice with 0 duration, once at the start and once at the
- finish.
:return: A TimedAction for that category and detail.
"""
- if allow_nested:
- result = NestingTimedAction(category, detail, self)
- else:
- result = TimedAction(category, detail, self)
+ result = TimedAction(category, detail, self)
if self.actions and self.actions[-1].duration is None:
raise OverlappingActionError(self.actions[-1], result)
self.actions.append(result)
=== modified file 'lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py'
--- lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py 2011-04-20 06:12:26 +0000
+++ lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py 2011-04-20 15:32:20 +0000
@@ -66,7 +66,7 @@
client.click(xpath='//*[@id="translation-incomplete-picker"]/a')
client.click(id='field.translations_usage.1')
client.click(
- xpath=overlay.visible_xpath + '//button[@type="submit"]')
+ xpath=overlay.visible_xpath + '//input[@value="Submit"]')
client.waits.forElementProperty(
id='translation-incomplete', option='className|sprite no unseen',
timeout=FOR_ELEMENT)
@@ -74,7 +74,7 @@
xpath='//*[@id="upstream-sync-incomplete-picker"]/a')
client.click(id='field.translations_autoimport_mode.2')
client.click(
- xpath=overlay.visible_xpath + '//button[@type="submit"]')
+ xpath=overlay.visible_xpath + '//input[@value="Submit"]')
client.waits.forElementProperty(
id='upstream-sync-incomplete',
option='className|sprite no unseen', timeout=FOR_ELEMENT)
=== modified file 'utilities/sourcedeps.conf'
--- utilities/sourcedeps.conf 2011-04-19 14:13:21 +0000
+++ utilities/sourcedeps.conf 2011-04-20 15:32:20 +0000
@@ -15,3 +15,4 @@
subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2042
python-debian lp:~launchpad-pqm/python-debian/devel;revno=186
testresources lp:~launchpad-pqm/testresources/dev;revno=16
+shipit lp:~launchpad-pqm/shipit/trunk;revno=8923 optional
=== modified file 'versions.cfg'
--- versions.cfg 2011-04-20 02:37:00 +0000
+++ versions.cfg 2011-04-20 15:32:20 +0000
@@ -32,7 +32,7 @@
lazr.batchnavigator = 1.2.2
lazr.config = 1.1.3
lazr.delegates = 1.2.0
-lazr.enum = 1.1.3
+lazr.enum = 1.1.2
lazr.lifecycle = 1.1
lazr.restful = 0.18.1
lazr.restfulclient = 0.11.2