Colin Watson has proposed merging lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad.
Commit message: Remove txlongpoll. Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~cjwatson/launchpad/remove-txlongpoll/+merge/366122 The Launchpad integration for this was never fully rolled out, is broken in some hard-to-fix ways, and is unlikely to be fixed. -- Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad.
=== modified file 'Makefile' --- Makefile 2018-06-06 12:46:56 +0000 +++ Makefile 2019-04-16 14:35:10 +0000 @@ -284,7 +284,7 @@ bin/test -f $(TESTFLAGS) $(TESTOPTS) run: build inplace stop - bin/run -r librarian,bing-webservice,memcached,rabbitmq,txlongpoll \ + bin/run -r librarian,bing-webservice,memcached,rabbitmq \ -i $(LPCONFIG) run-testapp: LPCONFIG=testrunner-appserver @@ -303,7 +303,7 @@ run_all: build inplace stop bin/run \ -r librarian,sftp,forker,mailman,codebrowse,bing-webservice,\ - memcached,rabbitmq,txlongpoll -i $(LPCONFIG) + memcached,rabbitmq -i $(LPCONFIG) run_codebrowse: compile BZR_PLUGIN_PATH=bzrplugins $(PY) scripts/start-loggerhead.py === modified file 'configs/development/launchpad-lazr.conf' --- configs/development/launchpad-lazr.conf 2018-06-14 16:28:45 +0000 +++ configs/development/launchpad-lazr.conf 2019-04-16 14:35:10 +0000 @@ -193,11 +193,6 @@ store_search_url: https://api.snapcraft.io/ tools_source: deb http://ppa.launchpad.net/snappy-dev/snapcraft-daily/ubuntu %(series)s main -[txlongpoll] -launch: True -frontend_port: 22435 -uri: /+longpoll/ - [rosetta] global_suggestions_enabled: True generate_templates: True === modified file 'configs/development/local-launchpad-apache' --- configs/development/local-launchpad-apache 2017-01-10 17:26:29 +0000 +++ configs/development/local-launchpad-apache 2019-04-16 14:35:10 +0000 @@ -32,7 +32,6 @@ SSLCertificateKeyFile /etc/apache2/ssl/launchpad.key ProxyPreserveHost on - ProxyPass /+longpoll/ http://localhost:22435/ retry=1 ProxyPass /+combo ! ProxyPass / http://localhost:8086/ retry=1 === modified file 'configs/testrunner/launchpad-lazr.conf' --- configs/testrunner/launchpad-lazr.conf 2018-05-22 07:53:52 +0000 +++ configs/testrunner/launchpad-lazr.conf 2019-04-16 14:35:10 +0000 @@ -164,11 +164,6 @@ password: none virtual_host: none -[txlongpoll] -launch: False -frontend_port: none -uri: none - [rosetta] generate_templates: True === modified file 'constraints.txt' --- constraints.txt 2019-04-08 07:13:18 +0000 +++ constraints.txt 2019-04-16 14:35:10 +0000 @@ -365,8 +365,6 @@ Twisted[conch,tls]==18.4.0 txAMQP==0.6.2 txfixtures==0.4.2 -txlongpoll==0.2.12 -txlongpollfixture==0.1.3 txpkgupload==0.2 typing==3.6.2 unittest2==1.1.0 === modified file 'database/sampledata/current-dev.sql' --- database/sampledata/current-dev.sql 2017-12-15 12:15:25 +0000 +++ database/sampledata/current-dev.sql 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ --- Copyright 2010-2017 Canonical Ltd. This software is licensed under the +-- Copyright 2010-2019 Canonical Ltd. This software is licensed under the -- GNU Affero General Public License version 3 (see the file LICENSE). -- Created using pg_dump (PostgreSQL) 9.3.5 @@ -3825,7 +3825,6 @@ ALTER TABLE featureflag DISABLE TRIGGER ALL; INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 0, 'js.combo_loader.enabled', 'true', '2012-05-18 07:34:39.239649'); -INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 1, 'longpoll.merge_proposals.enabled', 'true', '2012-05-18 07:34:39.239649'); ALTER TABLE featureflag ENABLE TRIGGER ALL; @@ -3833,7 +3832,6 @@ ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL; -INSERT INTO featureflagchangelogentry (id, date_changed, diff, comment, person) VALUES (1, '2011-10-06 12:44:04.37357', '+longpoll.merge_proposals.enabled default 1 true', 'Enable long-poll for merge proposals in development.', 16); ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL; === removed file 'lib/lp/app/javascript/longpoll.js' --- lib/lp/app/javascript/longpoll.js 2017-07-20 13:29:41 +0000 +++ lib/lp/app/javascript/longpoll.js 1970-01-01 00:00:00 +0000 @@ -1,217 +0,0 @@ -/* Copyright 2011 Canonical Ltd. This software is licensed under the - * GNU Affero General Public License version 3 (see the file LICENSE). - * - * The Launchpad Longpoll module provides the functionnality to deal - * with longpolling on the JavaScript side. - * - * The module method setupLongPollManager is called in every template and - * the long poll machinery will only be started if LP.cache.longpoll - * is populated. - * - * Usually the only thing you will want to do to use the long polling feature - * is: - * - * a) to make sure LP.cache.longpoll is populated with 'key' and 'uri'. - * - * b) to create Javascript handlers for the events which will be fired: - * - event_key will be fired when the event on the server side is triggered - * (event_key being the name of the event on the server side). - * - see below for other events fired by the longpoll machinery. - * - * @module longpoll - */ -YUI.add('lp.app.longpoll', function(Y) { - -var namespace = Y.namespace('lp.app.longpoll'); - -// Event fired when the long polling request starts. -namespace.longpoll_start_event = 'lp.app.longpoll.start'; - -// Event fired each time the long polling request fails (to connect or -// to parse the returned result). -namespace.longpoll_fail_event = 'lp.app.longpoll.failure'; - -// Event fired when the delay between each failed connection is set to -// a long delay (after MAX_SHORT_DELAY_FAILED_ATTEMPTS failed attempts). -namespace.longpoll_longdelay = 'lp.app.longpoll.longdelay'; - -// Event fired when the delay between each failed connection is set back -// to a short delay. -namespace.longpoll_shortdelay = 'lp.app.longpoll.shortdelay'; - -namespace._manager = null; - -// After MAX_SHORT_DELAY_FAILED_ATTEMPTS failed connections (real failed -// connections or connection getting an invalid return) separated -// by SHORT_DELAY (millisec), wait LONG_DELAY (millisec) between -// each failed connection. -namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS = 5; -namespace.SHORT_DELAY = 1000; -namespace.LONG_DELAY = 3*60*1000; - -/** - * - * A Long Poll Manager creates and manages a long polling connexion - * to the server to fetch events. This class is not directly used - * but managed through 'setupLongPollManager' which creates and - * initialises a singleton LongPollManager. - * - * @class LongPollManager - */ -function LongPollManager(config) { - LongPollManager.superclass.constructor.apply(this, arguments); -} - -LongPollManager.NAME = "longPollManager"; - -Y.extend(LongPollManager, Y.Base, { - initializer : function(cfg) { - this._started = false; - this._failed_attempts = 0; - this._repoll = true; - this._sequence = 0; - }, - - setConnectionInfos : function(key, uri) { - this.key = key; - this.uri = uri; - }, - - _io : function (uri, config) { - Y.io(uri, config); - }, - - successPoll : function (id, response) { - try { - var data = Y.JSON.parse(response.responseText); - if (!data.hasOwnProperty('event_key')) { - throw new Error("Response has no event_key"); - } - Y.fire(data.event_key, data); - return true; - } - catch (e) { - Y.fire(namespace.longpoll_fail_event, e); - return false; - } - }, - - failurePoll : function () { - Y.fire(namespace.longpoll_fail_event); - }, - - /** - * Return the delay (milliseconds) to wait before trying to reconnect - * again after a failed connection. - * - * The rationale here is that: - * 1. We should not try to reconnect instantaneously after a failed - * connection. - * 2. After a certain number of failed connections, we should set the - * delay between two failed connection to a bigger number because - * the server may be having problems. - * - * @method _pollDelay - */ - _pollDelay : function() { - this._failed_attempts = this._failed_attempts + 1; - if (this._failed_attempts >= - namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS) { - Y.fire(namespace.longpoll_longdelay); - return namespace.LONG_DELAY; - } - else { - return namespace.SHORT_DELAY; - } - }, - - /** - * Relaunch a connection to the server after a successful or - * a failed connection. - * - * @method repoll - * @param {Boolean} failed: whether or not the previous connection - * has failed. - */ - repoll : function(failed) { - if (!failed) { - if (this._failed_attempts >= - namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS) { - Y.fire(namespace.longpoll_shortdelay); - } - this._failed_attempts = 0; - if (this._repoll) { - this.poll(); - } - } - else { - var delay = this._pollDelay(); - if (this._repoll) { - Y.later(delay, this, this.poll); - } - } - }, - - poll : function() { - var that = this; - var config = { - method: "GET", - sync: false, - on: { - failure: function(id, response) { - if (Y.Lang.isValue(response) && - Y.Lang.isValue(response.status) && - (response.status === 408 || - response.status === 504)) { - // If the error code is: - // - 408 Request timeout - // - 504 Gateway timeout - // Then ignore the error and start - // polling again. - that.repoll(false); - } - else { - that.failurePoll(); - that.repoll(true); - } - }, - success: function(id, response) { - var res = that.successPoll(id, response); - that.repoll(res); - } - } - }; - this._sequence = this._sequence + 1; - var queue_uri = this.uri + - "?uuid=" + this.key + - "&sequence=" + this._sequence; - if (!this._started) { - Y.fire(namespace.longpoll_start_event); - this._started = true; - } - this._io(queue_uri, config); - } -}); - -namespace.LongPollManager = LongPollManager; - -namespace.getLongPollManager = function() { - if (!Y.Lang.isValue(namespace._manager)) { - namespace._manager = new namespace.LongPollManager(); - } - return namespace._manager; -}; - -namespace.setupLongPollManager = function() { - if (Y.Object.owns(LP.cache, 'longpoll') && - Y.Lang.isValue(LP.cache.longpoll)) { - var key = LP.cache.longpoll.key; - var uri = LP.cache.longpoll.uri; - var longpollmanager = namespace.getLongPollManager(); - longpollmanager.setConnectionInfos(key, uri); - longpollmanager.poll(); - return longpollmanager; - } -}; - -}, "0.1", {"requires":["base", "event", "json", "io"]}); === removed file 'lib/lp/app/javascript/tests/test_longpoll.html' --- lib/lp/app/javascript/tests/test_longpoll.html 2012-10-26 09:54:28 +0000 +++ lib/lp/app/javascript/tests/test_longpoll.html 1970-01-01 00:00:00 +0000 @@ -1,47 +0,0 @@ -<!DOCTYPE html> -<!-- -Copyright 2012 Canonical Ltd. This software is licensed under the -GNU Affero General Public License version 3 (see the file LICENSE). ---> - -<html> - <head> - <title>Test longpoll</title> - - <!-- YUI and test setup --> - <script type="text/javascript" - src="../../../../../build/js/yui/yui/yui.js"> - </script> - <link rel="stylesheet" - href="../../../../../build/js/yui/console/assets/console-core.css" /> - <link rel="stylesheet" - href="../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" /> - <link rel="stylesheet" - href="../../../../../build/js/yui/test/assets/skins/sam/test.css" /> - - <script type="text/javascript" - src="../../../../../build/js/lp/app/testing/testrunner.js"></script> - - <link rel="stylesheet" href="../../../app/javascript/testing/test.css" /> - - <!-- Dependencies --> - <script type="text/javascript" - src="../../../../../build/js/lp/app/client.js"></script> - - <!-- The module under test. --> - <script type="text/javascript" src="../longpoll.js"></script> - - <!-- Placeholder for any css asset for this module. --> - <!-- <link rel="stylesheet" href="../assets/longpoll-core.css" /> --> - - <!-- The test suite. --> - <script type="text/javascript" src="test_longpoll.js"></script> - - </head> - <body class="yui3-skin-sam"> - <ul id="suites"> - <!-- <li>lp.large_indicator.test</li> --> - <li>lp.longpoll.test</li> - </ul> - </body> -</html> === removed file 'lib/lp/app/javascript/tests/test_longpoll.js' --- lib/lp/app/javascript/tests/test_longpoll.js 2017-07-24 15:37:03 +0000 +++ lib/lp/app/javascript/tests/test_longpoll.js 1970-01-01 00:00:00 +0000 @@ -1,283 +0,0 @@ -/* Copyright 2011 Canonical Ltd. This software is licensed under the - * GNU Affero General Public License version 3 (see the file LICENSE). */ -YUI.add('lp.longpoll.test', function (Y) { - var longpoll = Y.lp.app.longpoll; - - var tests = Y.namespace('lp.longpoll.test'); - tests.suite = new Y.Test.Suite('longpoll Tests'); - - tests.suite.add(new Y.Test.Case({ - name: 'TestLongPollSingleton', - tearDown: function() { - // Cleanup the singleton; - longpoll._manager = null; - }, - - testGetSingletonLongPollManager: function() { - Y.Assert.isNull(longpoll._manager); - var manager = longpoll.getLongPollManager(); - Y.Assert.isNotNull(longpoll._manager); - var manager2 = longpoll.getLongPollManager(); - Y.Assert.areSame(manager, manager2); - }, - - testInitLongPollManagerNoLongPoll: function() { - // if LP.cache.longpoll.key is undefined: no longpoll manager - // is created by setupLongPollManager. - window.LP = { - links: {}, - cache: {} - }; - - longpoll.setupLongPollManager(true); - Y.Assert.isNull(longpoll._manager); - }, - - testInitLongPollManagerLongPoll: function() { - window.LP = { - links: {}, - cache: { - longpoll: { - key: 'key', - uri: '/+longpoll/' - } - } - }; - - longpoll.setupLongPollManager(true); - Y.Assert.isNotNull(longpoll._manager); - } - })); - - tests.suite.add(new Y.Test.Case({ - name: 'TestLongPoll', - - setUp: function() { - var manager = longpoll.getLongPollManager(); - manager._repoll = false; - this.createBaseLP(); - }, - - tearDown: function() { - // Cleanup the singleton; - longpoll._manager = null; - }, - - createBaseLP:function() { - window.LP = { - links: {}, - cache: {} - }; - }, - - setupLPCache: function() { - LP.cache.longpoll = { - key: 'key', - uri: '/+longpoll/' - }; - }, - - setupLongPoll: function(nb_calls) { - this.setupLPCache(); - return longpoll.setupLongPollManager(true); - }, - - testInitLongPollManagerQueueName: function() { - var manager = this.setupLongPoll(); - Y.Assert.areEqual(LP.cache.longpoll.key, manager.key); - Y.Assert.areEqual(LP.cache.longpoll.uri, manager.uri); - Y.Assert.isFalse(Y.Lang.isValue(manager.nb_calls)); - }, - - testPollStarted: function() { - var fired = false; - Y.on(longpoll.longpoll_start_event, function() { - fired = true; - }); - this.setupLongPoll(); - Y.Assert.isTrue(fired, "Start event not fired."); - }, - - testPollFailure: function() { - var fired = false; - Y.on(longpoll.longpoll_fail_event, function() { - fired = true; - }); - // Monkeypatch io to simulate failure. - var manager = longpoll.getLongPollManager(); - manager._io = function(uri, config) { - config.on.failure(); - }; - this.setupLongPoll(); - Y.Assert.isTrue(fired, "Failure event not fired."); - }, - - testSuccessPollInvalidData: function() { - var manager = longpoll.getLongPollManager(); - var custom_response = "{{"; - var response = { - responseText: custom_response - }; - var res = manager.successPoll("2", response); - Y.Assert.isFalse(res); - }, - - testSuccessPollMalformedData: function() { - var manager = longpoll.getLongPollManager(); - var response = { - responseText: '{ "something": "6" }' - }; - var res = manager.successPoll("2", response); - Y.Assert.isFalse(res); - }, - - testSuccessPollWellformedData: function() { - var manager = longpoll.getLongPollManager(); - var response = { - responseText: '{ "event_key": "4", "something": "6"}' - }; - var res = manager.successPoll("2", response); - Y.Assert.isTrue(res); - }, - - testPollDelay: function() { - // Create event listeners. - var longdelay_event_fired = false; - Y.on(longpoll.longpoll_longdelay, function(data) { - longdelay_event_fired = true; - }); - var shortdelay_event_fired = false; - Y.on(longpoll.longpoll_shortdelay, function(data) { - shortdelay_event_fired = true; - }); - var manager = longpoll.getLongPollManager(); - // Monkeypatch io to simulate failure. - manager._io = function(uri, config) { - config.on.failure(); - }; - Y.Assert.areEqual(0, manager._failed_attempts); - this.setupLongPoll(); - Y.Assert.areEqual(1, manager._failed_attempts); - var i, delay; - for (i=0; i<longpoll.MAX_SHORT_DELAY_FAILED_ATTEMPTS-2; i++) { - Y.Assert.areEqual(i+1, manager._failed_attempts); - delay = manager._pollDelay(); - Y.Assert.areEqual(delay, longpoll.SHORT_DELAY); - } - // After MAX_SHORT_DELAY_FAILED_ATTEMPTS failed attempts, the - // delay returned by _pollDelay is LONG_DELAY and - // longpoll_longdelay is fired. - Y.Assert.isFalse(longdelay_event_fired); - delay = manager._pollDelay(); - Y.Assert.isTrue(longdelay_event_fired); - Y.Assert.areEqual(delay, longpoll.LONG_DELAY); - - // Monkeypatch io to simulate success. - manager._io = function(uri, config) { - config.on.success(); - }; - // After a success, longpoll.longpoll_shortdelay is fired. - Y.Assert.isFalse(shortdelay_event_fired); - delay = manager.poll(); - Y.Assert.isTrue(shortdelay_event_fired); - }, - - testPollUriSequence: function() { - // Each new polling increases the sequence parameter: - // /+longpoll/?uuid=key&sequence=1 - // /+longpoll/?uuid=key&sequence=2 - // /+longpoll/?uuid=key&sequence=3 - // .. - var count = 0; - // Monkeypatch io to simulate failure. - var manager = longpoll.getLongPollManager(); - manager._io = function(uri, config) { - Y.Assert.areEqual( - '/+longpoll/?uuid=key&sequence=' + (count+1), - uri); - count = count + 1; - var response = { - responseText: '{"i":2}' - }; - config.on.success(2, response); - }; - this.setupLongPoll(); - var request; - for (request=1; request<10; request++) { - Y.Assert.isTrue(count === request, "Uri not requested."); - manager.poll(); - } - }, - - _testDoesNotFail: function(error_code) { - // Assert that, when the longpoll request receives an error - // with code error_code, it is not treated as a failed - // connection attempt. - var manager = longpoll.getLongPollManager(); - // Monkeypatch io to simulate a request timeout. - manager._io = function(uri, config) { - var response = {status: error_code}; - config.on.failure(4, response); - }; - - Y.Assert.areEqual(0, manager._failed_attempts); - this.setupLongPoll(); - Y.Assert.areEqual(0, manager._failed_attempts); - }, - - test408RequestTimeoutHandling: function() { - this._testDoesNotFail(408); - }, - - test504GatewayTimeoutHandling: function() { - this._testDoesNotFail(504); - }, - - testPollPayLoadBad: function() { - // If a non valid response is returned, longpoll_fail_event - // is fired. - var fired = false; - Y.on(longpoll.longpoll_fail_event, function() { - fired = true; - }); - var manager = longpoll.getLongPollManager(); - // Monkeypatch io. - manager._io = function(uri, config) { - var response = { - responseText: "{non valid json" - }; - config.on.success(2, response); - }; - this.setupLongPoll(); - Y.Assert.isTrue(fired, "Failure event not fired."); - }, - - testPollPayLoadOk: function() { - // Create a valid message. - var custom_response = { - 'event_key': 'my-event', - 'something': {something_else: 1234} - }; - var fired = false; - Y.on(custom_response.event_key, function(data) { - fired = true; - Y.Assert.areEqual(data, custom_response); - }); - var manager = longpoll.getLongPollManager(); - // Monkeypatch io. - manager._io = function(uri, config) { - var response = { - responseText: Y.JSON.stringify(custom_response) - }; - config.on.success(2, response); - }; - this.setupLongPoll(); - Y.Assert.isTrue(fired, "Custom event not fired."); - } - })); - -}, '0.1', { - requires: [ - 'lp.testing.runner', 'test', 'test-console', 'node-event-simulate', - 'json', 'lp.app.longpoll'] -}); === removed directory 'lib/lp/app/longpoll' === removed file 'lib/lp/app/longpoll/__init__.py' --- lib/lp/app/longpoll/__init__.py 2012-02-21 22:46:28 +0000 +++ lib/lp/app/longpoll/__init__.py 1970-01-01 00:00:00 +0000 @@ -1,50 +0,0 @@ -# Copyright 2011-2012 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll infrastructure.""" - -__metaclass__ = type -__all__ = [ - "emit", - "subscribe", - ] - -from lazr.restful.utils import get_current_browser_request -from zope.component import getAdapter - -from lp.services.longpoll.interfaces import ( - ILongPollEvent, - ILongPollSubscriber, - ) - - -def subscribe(target, event_name=u"", request=None): - """Convenience method to subscribe the current request. - - :param target: Something that can be adapted to `ILongPollEvent`. - :param event_name: The name of the event to subscribe to. This is used to - look up a named adapter from `target` to `ILongPollEvent`. - :param request: The request for which to get an `ILongPollSubscriber`. It - a request is not specified the currently active request is used. - :return: The `ILongPollEvent` that has been subscribed to. - """ - event = getAdapter(target, ILongPollEvent, name=event_name) - if request is None: - request = get_current_browser_request() - subscriber = ILongPollSubscriber(request) - subscriber.subscribe(event) - return event - - -def emit(source, event_name=u"", **data): - """Convenience method to emit a message for an event. - - :param source: Something that can be adapted to `ILongPollEvent`. - :param event_name: The name of the event to subscribe to. This is used to - look up a named adapter from `target` to `ILongPollEvent`. - :param data: See `ILongPollEvent.emit`. - :return: The `ILongPollEvent` that has been emitted. - """ - event = getAdapter(source, ILongPollEvent, name=event_name) - event.emit(**data) - return event === removed directory 'lib/lp/app/longpoll/tests' === removed file 'lib/lp/app/longpoll/tests/__init__.py' === removed file 'lib/lp/app/longpoll/tests/test_longpoll.py' --- lib/lp/app/longpoll/tests/test_longpoll.py 2015-07-09 12:18:51 +0000 +++ lib/lp/app/longpoll/tests/test_longpoll.py 1970-01-01 00:00:00 +0000 @@ -1,121 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Tests for lp.app.longpoll.""" - -__metaclass__ = type - -from zope.component import ( - adapter, - getUtility, - ) -from zope.interface import ( - Attribute, - implementer, - Interface, - ) - -from lp.app.longpoll import ( - emit, - subscribe, - ) -from lp.services.longpoll.interfaces import ( - ILongPollEvent, - ILongPollSubscriber, - ) -from lp.services.messaging.interfaces import IMessageSession -from lp.services.webapp.servers import LaunchpadTestRequest -from lp.testing import TestCase -from lp.testing.fixture import ZopeAdapterFixture -from lp.testing.layers import LaunchpadFunctionalLayer - - -class IFakeObject(Interface): - - ident = Attribute("ident") - - -@implementer(IFakeObject) -class FakeObject: - - def __init__(self, ident): - self.ident = ident - - -@adapter(IFakeObject) -@implementer(ILongPollEvent) -class FakeEvent: - - def __init__(self, source): - self.source = source - - @property - def event_key(self): - return "event-key-%s" % self.source.ident - - def emit(self, **data): - # Don't cargo-cult this; see .adapters.event.LongPollEvent instead. - session = getUtility(IMessageSession) - producer = session.getProducer(self.event_key) - producer.sendNow(data) - - -class TestFunctions(TestCase): - - layer = LaunchpadFunctionalLayer - - def test_subscribe(self): - # subscribe() gets the ILongPollEvent for the given target and the - # ILongPollSubscriber for the given request (or the current request is - # discovered). It subscribes the latter to the event, then returns the - # event. - session = getUtility(IMessageSession) - request = LaunchpadTestRequest() - an_object = FakeObject(12345) - with ZopeAdapterFixture(FakeEvent): - event = subscribe(an_object, request=request) - self.assertIsInstance(event, FakeEvent) - self.assertEqual("event-key-12345", event.event_key) - session.flush() - # Emitting an event-key-12345 event will put something on the - # subscriber's queue. - event_data = {"1234": 5678} - event.emit(**event_data) - subscriber = ILongPollSubscriber(request) - subscribe_queue = session.getConsumer(subscriber.subscribe_key) - message = subscribe_queue.receive(timeout=5) - self.assertEqual(event_data, message) - - def test_subscribe_to_named_event(self): - # When an event_name is given to subscribe(), a named adapter is used - # to get the ILongPollEvent for the given target. - request = LaunchpadTestRequest() - an_object = FakeObject(12345) - with ZopeAdapterFixture(FakeEvent, name="foo"): - event = subscribe(an_object, event_name="foo", request=request) - self.assertIsInstance(event, FakeEvent) - - def test_emit(self): - # emit() gets the ILongPollEvent for the given target and passes the - # given data to its emit() method. It then returns the event. - an_object = FakeObject(12345) - with ZopeAdapterFixture(FakeEvent): - event = emit(an_object) - session = getUtility(IMessageSession) - producer = session.getProducer(event.event_key) - subscribe_queue = session.getConsumer("whatever") - producer.associateConsumerNow(subscribe_queue) - # Emit the event again; the subscribe queue was not associated - # with the event before now. - event_data = {"8765": 4321} - event = emit(an_object, **event_data) - message = subscribe_queue.receive(timeout=5) - self.assertEqual(event_data, message) - - def test_emit_named_event(self): - # When an event_name is given to emit(), a named adapter is used to - # get the ILongPollEvent for the given target. - an_object = FakeObject(12345) - with ZopeAdapterFixture(FakeEvent, name="foo"): - event = emit(an_object, "foo") - self.assertIsInstance(event, FakeEvent) === modified file 'lib/lp/app/templates/base-layout-macros.pt' --- lib/lp/app/templates/base-layout-macros.pt 2019-01-05 09:54:44 +0000 +++ lib/lp/app/templates/base-layout-macros.pt 2019-04-16 14:35:10 +0000 @@ -120,7 +120,7 @@ //<![CDATA[ LPJS.use('base', 'node', 'console', 'event', 'oop', 'lp', 'lp.app.foldables','lp.app.sorttable', - 'lp.app.inlinehelp', 'lp.app.links', 'lp.app.longpoll', + 'lp.app.inlinehelp', 'lp.app.links', 'lp.bugs.bugtask_index', 'lp.bugs.subscribers', 'lp.app.ellipsis', 'lp.code.branchmergeproposal.diff', 'lp.views.global', @@ -135,14 +135,6 @@ Y.lp.activate_collapsibles(); Y.lp.app.foldables.activate(); Y.lp.app.links.check_valid_lp_links(); - // Longpolling will only start if - // LP.cache.longpoll is populated. - // We use Y.later to work around a Safari/Chrome 'feature': - // The mouse cursor stays 'busy' until all the requests started during - // page load are finished. Hence we want the long poll request to start - // right *after* the page has loaded. - Y.later(0, Y.lp.app.longpoll, Y.lp.app.longpoll.setupLongPollManager); - }); Y.on('lp:context:web_link:changed', function(e) { === modified file 'lib/lp/code/browser/branchmergeproposal.py' --- lib/lp/code/browser/branchmergeproposal.py 2019-01-31 14:45:32 +0000 +++ lib/lp/code/browser/branchmergeproposal.py 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Views, navigation and actions for BranchMergeProposals.""" @@ -69,7 +69,6 @@ vocabulary_to_choice_edit_items, ) from lp.app.browser.tales import DateTimeFormatterAPI -from lp.app.longpoll import subscribe from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta from lp.code.browser.codereviewcomment import CodeReviewDisplayComment from lp.code.browser.decorations import DecoratedBranch @@ -628,9 +627,6 @@ else: cache.objects['branch_diff_link'] = ( canonical_url(self.context.parent) + '/+diff/') - if getFeatureFlag("longpoll.merge_proposals.enabled"): - cache.objects['merge_proposal_event_key'] = subscribe( - self.context).event_key @action('Claim', name='claim') def claim_action(self, action, data): === modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py' --- lib/lp/code/browser/tests/test_branchmergeproposal.py 2019-01-31 14:21:09 +0000 +++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Unit tests for BranchMergeProposals.""" @@ -1576,26 +1576,6 @@ git_api.notify(bmp.source_git_repository.getInternalPath())) self.assertTrue(view.pending_diff) - def test_subscribe_to_merge_proposal_events_flag_disabled(self): - # If the longpoll.merge_proposals.enabled flag is not enabled the user - # is *not* subscribed to events relating to the merge proposal. - bmp = self.factory.makeBranchMergeProposal() - view = create_initialized_view(bmp, '+index', current_request=True) - cache = IJSONRequestCache(view.request) - self.assertNotIn("longpoll", cache.objects) - self.assertNotIn("merge_proposal_event_key", cache.objects) - - def test_subscribe_to_merge_proposal_events_flag_enabled(self): - # If the longpoll.merge_proposals.enabled flag is enabled the user is - # subscribed to events relating to the merge proposal. - bmp = self.factory.makeBranchMergeProposal() - self.useContext(feature_flags()) - set_feature_flag('longpoll.merge_proposals.enabled', 'enabled') - view = create_initialized_view(bmp, '+index', current_request=True) - cache = IJSONRequestCache(view.request) - self.assertIn("longpoll", cache.objects) - self.assertIn("merge_proposal_event_key", cache.objects) - def test_description_is_meta_description(self): description = ( "I'd like to make the bmp description appear as the meta " @@ -2076,20 +2056,6 @@ browser = self.getViewBrowser(bmp) assert 'unf_pbasyvpgf' in browser.contents - def test_pending_diff_message_with_longpoll_enabled(self): - # If the longpoll feature flag is enabled then the message - # displayed for a pending diff indicates that it'll update - # automatically. See also - # lib/lp/code/stories/branches/xx-branchmergeproposals.txt - self.useContext(feature_flags()) - set_feature_flag('longpoll.merge_proposals.enabled', 'enabled') - bmp = self.factory.makeBranchMergeProposal() - browser = self.getViewBrowser(bmp) - self.assertIn( - "An updated diff is being calculated and will appear " - "automatically when ready.", - browser.contents) - def test_short_conversation_comments_not_truncated(self): """Short comments should not be truncated.""" comment = self.factory.makeCodeReviewComment(body='x y' * 100) === removed file 'lib/lp/code/javascript/branchmergeproposal.updater.js' --- lib/lp/code/javascript/branchmergeproposal.updater.js 2014-03-31 19:40:35 +0000 +++ lib/lp/code/javascript/branchmergeproposal.updater.js 1970-01-01 00:00:00 +0000 @@ -1,263 +0,0 @@ -/* Copyright 2011 Canonical Ltd. This software is licensed under the - * GNU Affero General Public License version 3 (see the file LICENSE). - * - * Code for updating the diff when a new version is available. - * - * @module lp.code.branchmergeproposal.updater - * @requires node, lp.client - */ - -YUI.add('lp.code.branchmergeproposal.updater', function(Y) { - -var namespace = Y.namespace('lp.code.branchmergeproposal.updater'); - -function UpdaterWidget(config) { - UpdaterWidget.superclass.constructor.apply(this, arguments); -} - -Y.mix(UpdaterWidget, { - - NAME: 'updaterWidget', - - ATTRS: { - - /** - * The LP client to use. If none is provided, one will be - * created during initialization. - * - * @attribute lp_client - */ - lp_client: { - value: null - }, - - /** - * The summary node. - * - * @attribute summary_node - */ - summary_node: { - value: null, - writeOnce: "initOnly" - }, - - /** - * Whether or not this MP is still 'pending'. - * - * @attribute pending - * @readOnly - */ - pending: { - readOnly: true, - getter: function() { - return !Y.Lang.isValue( - this.get('srcNode').one('.diff-content')); - } - }, - - /** - * The HTML code for the stats diff. - * - * @attribute diff_stats - */ - diff_stats: { - getter: function() { - var summary_node = this.get('summary_node'); - if (!Y.Lang.isValue(summary_node) || - !Y.Lang.isValue(summary_node.one( - '#summary-row-b-diff'))) { - return null; - } - return summary_node.one( - '#summary-row-b-diff').one('td').get('innerHTML'); - }, - setter: function(value) { - this._setup_diff_stats_container(); - var container = this.get( - 'summary_node').one('#summary-row-b-diff').one('td'); - container.set('innerHTML', value); - } - } - } - -}); - -Y.extend(UpdaterWidget, Y.Widget, { - - /* - * The initializer method that is called from the base Plugin class. - * - * @method initializer - * @protected - */ - initializer: function(cfg){ - // If we have not been provided with a Launchpad Client, then - // create one now: - if (null === this.get("lp_client")){ - // Create our own instance of the LP client. - this.set("lp_client", new Y.lp.client.Launchpad()); - } - this.set('summary_node', cfg.summary_node); - }, - - /* - * Set the proper icon to indicate the diff is updating. - * - * @method set_status_updating - */ - set_status_updating: function() { - this.cleanup_status(); - this._set_status('spinner', 'Update in progress.'); - }, - - /* - * Set the proper icon to indicate the diff will be updated when the - * new version is available. - * - * @method set_status_longpolling - */ - set_status_longpolling: function() { - this.cleanup_status(); - this._set_status( - 'longpoll_loading', - 'The diff will be updated as soon as a new version is available.'); - }, - - /* - * Set the proper icon to indicate that the diff update is broken. - * - * @method set_status_longpollerror - */ - set_status_longpollerror: function() { - this.cleanup_status(); - this._set_status( - 'longpoll_error', - 'Diff update error, please reload to see the changes.'); - }, - - /* - * Add a status image to the diff title. - * - * @method _set_status - */ - _set_status: function(image_name, title) { - var image = Y.Node.create('<img />') - .set('src', '/@@/' + image_name) - .set('title', title); - this.get('srcNode').one('h2').append(image); - }, - - /* - * Remove the status image to the diff title. - * - * @method cleanup_status - */ - cleanup_status: function() { - this._setup_diff_container(); - this.get('srcNode').all('h2 img').remove(); - }, - - /* - * Add a row in the page summary table to display the diff stats - * if needed. - * - * @method _setup_diff_stats_container - */ - _setup_diff_stats_container: function() { - if (!Y.Lang.isValue(this.get('diff_stats'))) { - var summary_node = this.get('summary_node'); - var diff_stats = Y.Node.create('<tr />') - .set('id', 'summary-row-b-diff') - .append(Y.Node.create('<th />') - .set("text", "Diff against target:")) - .append(Y.Node.create('<td />')); - summary_node.one( - '#summary-row-9-target-branch').insert(diff_stats, 'after'); - } - }, - - /* - * Populate the widget with the required nodes to display the diff - * if needed. - * - * @method _setup_diff_container - */ - _setup_diff_container: function() { - if (this.get('pending')) { - // Cleanup.get('srcNode'). - this.get('srcNode').empty(); - // Create the diff container. - var review_diff = Y.Node.create('<div />') - .set('id', 'review-diff') - .append(Y.Node.create('<h2 />') - .set("text", "Preview Diff ")) - .append(Y.Node.create('<div />') - .addClass("diff-navigator")) - .append(Y.Node.create('<div />') - .addClass("diff-content")); - this.get('srcNode').append(review_diff); - } - }, - - /* - * Update the page diff stats. - * - * @method update - */ - update: function() { - this.update_stats(); - }, - - /* - * Update the diff stats with the last version. - * - * @method update_stats - */ - update_stats: function() { - var self = this; - var config = { - on: { - success: function(diff_stats) { - self.set('diff_stats', diff_stats); - // (re)connect the js scroller link. - Y.lp.code.branchmergeproposal.reviewcomment.link_scroller( - '#proposal-summary a.diff-link', '#review-diff'); - var node = self.get('summary_node'); - Y.lp.anim.green_flash({node: node}).run(); - self.fire(self.NAME + '.updated'); - }, - failure: function() { - var node = self.get('summary_node'); - Y.lp.anim.red_flash({node: node}).run(); - }, - start: function() { - self.set_status_updating(); - }, - end: function() { - self.cleanup_status(); - } - } - }; - var mp_uri = LP.cache.context.web_link; - this.get('lp_client').get(mp_uri + "/++diff-stats", config); - } - -}); - -/* - * Export UpdaterWidget. - */ -namespace.UpdaterWidget = UpdaterWidget; - -/* - * Returns true if the event fired means that the preview_diff field of the - * MP has been updated. - * - */ -namespace.is_mp_diff_updated = function(event_data) { - return (event_data.what === "modified" && - event_data.edited_fields.indexOf("preview_diff") >= 0); -}; - -}, '0.1', {requires: ['node', 'lp.client', 'lp.anim', - 'lp.code.branchmergeproposal.reviewcomment']}); === removed file 'lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html' --- lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html 2012-10-26 09:54:28 +0000 +++ lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html 1970-01-01 00:00:00 +0000 @@ -1,98 +0,0 @@ -<!DOCTYPE html> -<!-- -Copyright 2012 Canonical Ltd. This software is licensed under the -GNU Affero General Public License version 3 (see the file LICENSE). ---> - -<html> - <head> - <title>Test branchmergeproposal</title> - - <!-- YUI and test setup --> - <script type="text/javascript" - src="../../../../../build/js/yui/yui/yui.js"> - </script> - <link rel="stylesheet" - href="../../../../../build/js/yui/console/assets/console-core.css" /> - <link rel="stylesheet" - href="../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" /> - <link rel="stylesheet" - href="../../../../../build/js/yui/test/assets/skins/sam/test.css" /> - - <script type="text/javascript" - src="../../../../../build/js/lp/app/testing/testrunner.js"></script> - - <link rel="stylesheet" href="../../../app/javascript/testing/test.css" /> - - <!-- Dependencies --> - <script type="text/javascript" - src="../../../../../build/js/lp/..."></script> - <script type="text/javascript" - src="../../../../../build/js/lp/app/client.js"></script> - <script type="text/javascript" - src="../../../../../build/js/lp/app/lp.js"></script> - <script type="text/javascript" - src="../../../../../build/js/lp/app/anim/anim.js"></script> - <script type="text/javascript" - src="../../../../../build/js/lp/app/extras/extras.js"></script> - <script type="text/javascript" - src="../../../../../build/js/lp/app/testing/mockio.js"></script> - <script type="text/javascript" - src="../../../../../build/js/lp/code/branchmergeproposal.reviewcomment.js"></script> - - <!-- The module under test. --> - <script type="text/javascript" src="../branchmergeproposal.updater.js"></script> - - <!-- Any css assert for this module. --> - <!-- <link rel="stylesheet" href="../assets/branchmergeproposal-core.css" /> --> - - <!-- The test suite. --> - <script type="text/javascript" src="test_branchmergeproposal.updater.js"></script> - - <!-- expected variable --> - <script type="text/javascript"> - var LP = { - cache: {}, - links: {} - }; - </script> - - </head> - <body class="yui3-skin-sam"> - <ul id="suites"> - <!-- <li>lp.large_indicator.test</li> --> - <li>lp.branchmergeproposal.updater.test</li> - </ul> - <div id="placeholder" style="display:none;"> - </div> - - <script type="text/x-template" id="pending-mp"> - <table id="proposal-summary"> - <tr id="summary-row-9-target-branch"> - <th>Merge into:</th> - <td> - <a href="/~me/project/branch" - class="sprite branch">lp://dev/~me/project/branch</a> - </td> - </tr> - </table> - <div id="diff-area"> - <div class="pending-update" id="diff-pending-update"> - <h3>Updating diff...</h3> - <p>An updated diff will be available in a few minutes.</p> - </div> - </div> - </script> - - <script type="text/x-template" id="current-mp"> - <div id="diff-area"> - <div id="review-diff"> - <h2>Preview Diff</h2> - <div class="diff-content">Example diff</div> - </div> - </div> - </script> - - - </body> -</html> === removed file 'lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js' --- lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js 2014-03-31 19:40:35 +0000 +++ lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js 1970-01-01 00:00:00 +0000 @@ -1,162 +0,0 @@ -/* Copyright 2011 Canonical Ltd. This software is licensed under the - * GNU Affero General Public License version 3 (see the file LICENSE). - * - * Tests for lp.code.branchmergeproposal.updater. - * - */ -YUI.add('lp.branchmergeproposal.updater.test', function (Y) { -var module = Y.lp.code.branchmergeproposal.updater; -var UpdaterWidget = module.UpdaterWidget; - - -var tests = Y.namespace('lp.branchmergeproposal.updater.test'); -tests.suite = new Y.Test.Suite("BranchMergeProposal Updater Tests"); - -/* - * Tests for when the updater is built on top of a pending diff. - * - */ - -var pending_mp = Y.one('#pending-mp').getContent(); - -tests.suite.add(new Y.Test.Case({ - - name: 'branchmergeproposal-updater-pending-tests', - - setUp: function() { - Y.one("#placeholder") - .empty() - .append(Y.Node.create(pending_mp)); - var diff_area = Y.one('#diff-area'); - var summary_node = Y.one('#proposal-summary'); - this.updater = new UpdaterWidget( - {srcNode: diff_area, summary_node: summary_node}); - - LP.cache.context = { - web_link: "https://code.launchpad.dev/~foo/bar/foobr/+merge/123"}; - - }, - - tearDown: function() { - this.updater.destroy(); - }, - - test_default_values: function() { - Y.Assert.isTrue(this.updater.get('pending')); - Y.Assert.isNull(this.updater.get('diff_stats')); - }, - - test__setup_diff_container: function() { - this.updater._setup_diff_container(); - Y.Assert.isFalse(this.updater.get('pending')); - Y.Assert.areEqual( - "Preview Diff ", - this.updater.get( - 'srcNode').one('#review-diff h2').get('text')); - Y.Assert.areEqual( - "", - this.updater.get( - 'srcNode').one('.diff-content').get('text')); - }, - - test__setup_diff_stats_container: function() { - Y.Assert.isNull(this.updater.get('diff_stats')); - this.updater._setup_diff_stats_container(); - Y.Assert.areEqual('', this.updater.get('diff_stats')); - }, - - test_set_diff_stats: function() { - this.updater.set('diff_stats', '13 lines (+4/-0) 1 file modified'); - Y.Assert.areEqual( - '13 lines (+4/-0) 1 file modified', - this.updater.get('diff_stats')); - }, - - test_set_status_updating: function() { - this.updater.set_status_updating(); - Y.Assert.areEqual( - '/@@/spinner', - Y.one('h2').one('img').getAttribute('src')); - }, - - test_set_status_longpolling: function() { - this.updater.set_status_longpolling(); - Y.Assert.areEqual( - '/@@/longpoll_loading', - Y.one('h2').one('img').getAttribute('src')); - }, - - test_set_status_longpollerror: function() { - this.updater.set_status_longpollerror(); - Y.Assert.areEqual( - '/@@/longpoll_error', - Y.one('h2').one('img').getAttribute('src')); - }, - - test_cleanup_status: function() { - this.updater._setup_diff_container(); - this.updater.set_status_updating(); - this.updater.cleanup_status(); - Y.Assert.areEqual( - 'Preview Diff ', - Y.one('h2').get('innerHTML')); - }, - - test_update_stats_success: function() { - var mockio = new Y.lp.testing.mockio.MockIo(); - this.updater.get('lp_client').io_provider = mockio; - Y.Assert.isNull(this.updater.get('diff_stats')); - this.updater.update_stats(); - mockio.success({ - responseText: '13 lines (+4/-0) 1 file modified', - responseHeaders: {'Content-Type': 'text/html'}}); - - Y.Assert.areEqual( - '13 lines (+4/-0) 1 file modified', - this.updater.get('diff_stats')); - }, - - test_update_fires_event: function() { - var fired = false; - var mockio = new Y.lp.testing.mockio.MockIo(); - this.updater.get('lp_client').io_provider = mockio; - this.updater.on(this.updater.NAME + '.updated', function() { - fired = true; - }); - this.updater.update(); - mockio.success({ - responseText: '13 lines (+4/-0) 1 file modified', - responseHeaders: {'Content-Type': 'text/html'}}); - Y.Assert.isTrue(fired); - } - -})); - - -tests.suite.add(new Y.Test.Case({ - - name: 'branchmergeproposal-updater-utilities', - - test_is_mp_diff_updated_modified: function() { - var data = {what: 'modified', edited_fields: ['preview_diff']}; - Y.Assert.isTrue(module.is_mp_diff_updated(data)); - }, - - test_is_mp_diff_updater_deleted: function() { - var data = {what: 'deleted'}; - Y.Assert.isFalse(module.is_mp_diff_updated(data)); - }, - - test_is_mp_diff_updated_title_changed: function() { - var data = {what: 'modified', edited_fields: ['title']}; - Y.Assert.isFalse(module.is_mp_diff_updated(data)); - } - -})); - - -}, '0.1', { - requires: ['lp.testing.runner', 'test', 'dump', 'test-console', 'node', - 'lp.testing.mockio', 'event', - 'lp.code.branchmergeproposal.updater'] -}); === modified file 'lib/lp/code/templates/branchmergeproposal-index.pt' --- lib/lp/code/templates/branchmergeproposal-index.pt 2019-01-31 13:48:34 +0000 +++ lib/lp/code/templates/branchmergeproposal-index.pt 2019-04-16 14:35:10 +0000 @@ -185,13 +185,10 @@ <div class="yui-g" tal:condition="python: not view.show_diff_update_link and view.pending_diff"> <div class="pending-update" id="diff-pending-update"> <h3>Updating diff...</h3> - <p tal:condition="not: features/longpoll.merge_proposals.enabled"> + <p> An updated diff will be available in a few minutes. Reload to see the changes. </p> - <p tal:condition="features/longpoll.merge_proposals.enabled"> - An updated diff is being calculated and will appear automatically when ready. - </p> </div> </div> <div class="yui-g" tal:condition="view/show_diff_update_link"> @@ -226,7 +223,7 @@ conf = <tal:status-config replace="view/status_config" /> LPJS.use('io-base', 'lp.code.branchmergeproposal.reviewcomment', 'lp.code.branchmergeproposal.status', 'lp.app.comment', - 'lp.code.branchmergeproposal.updater', 'lp.app.widgets.expander', + 'lp.app.widgets.expander', 'lp.code.branch.revisionexpander', 'lp.code.branchmergeproposal.inlinecomments', function(Y) { @@ -252,23 +249,6 @@ diffnav.render(); } - if (Y.Lang.isValue(LP.cache.merge_proposal_event_key)) { - var upt = Y.lp.code.branchmergeproposal.updater; - var cfg = { - srcNode: Y.one('#diff-area'), - summary_node: Y.one('#proposal-summary') - }; - var updater = new upt.UpdaterWidget(cfg); - Y.on(LP.cache.merge_proposal_event_key, function(data) { - if (upt.is_mp_diff_updated(data)) { - updater.update(); - } - }); - updater.on(updater.NAME + '.updated', function() { - diffnav.render(); - }); - } - LP.cache.comment_context = LP.cache.context; var cl = new Y.lp.app.comment.CommentList({ comment_list_container: Y.one('#conversation') === modified file 'lib/lp/scripts/runlaunchpad.py' --- lib/lp/scripts/runlaunchpad.py 2018-05-21 20:30:16 +0000 +++ lib/lp/scripts/runlaunchpad.py 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). __metaclass__ = type @@ -27,7 +27,6 @@ ) from lp.services.rabbit.server import RabbitServer from lp.services.sitesearch import bingtestservice -from lp.services.txlongpoll.server import TxLongPollServer def make_abspath(path): @@ -237,28 +236,6 @@ self.useFixture(self.server) -class TxLongPollService(Service): - """A TxLongPoll service.""" - - @property - def should_launch(self): - return config.txlongpoll.launch - - def launch(self): - twistd_bin = os.path.join(config.root, 'bin', 'twistd') - broker_hostname, broker_port = as_host_port( - config.rabbitmq.host, None, None) - self.server = TxLongPollServer( - twistd_bin=twistd_bin, - frontend_port=config.txlongpoll.frontend_port, - broker_user=config.rabbitmq.userid, - broker_password=config.rabbitmq.password, - broker_vhost=config.rabbitmq.virtual_host, - broker_host=broker_hostname, - broker_port=broker_port) - self.useFixture(self.server) - - def stop_process(process): """kill process and BLOCK until process dies. @@ -284,7 +261,6 @@ 'codebrowse': CodebrowseService(), 'memcached': MemcachedService(), 'rabbitmq': RabbitService(), - 'txlongpoll': TxLongPollService(), } === modified file 'lib/lp/scripts/tests/test_runlaunchpad.py' --- lib/lp/scripts/tests/test_runlaunchpad.py 2018-05-21 20:30:16 +0000 +++ lib/lp/scripts/tests/test_runlaunchpad.py 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Tests for runlaunchpad.py""" @@ -156,10 +156,6 @@ if config.rabbitmq.launch: expected.append(SERVICES['rabbitmq']) - # TxLongPoll may or may not be asked to run. - if config.txlongpoll.launch: - expected.append(SERVICES['txlongpoll']) - expected = sorted(expected) self.assertEqual(expected, services) === modified file 'lib/lp/services/config/schema-lazr.conf' --- lib/lp/services/config/schema-lazr.conf 2019-03-26 20:51:38 +0000 +++ lib/lp/services/config/schema-lazr.conf 2019-04-16 14:35:10 +0000 @@ -1601,16 +1601,6 @@ # datatype: string virtual_host: none -[txlongpoll] -# Should TxLongPoll be launched by default? -# datatype: boolean -launch: False -# The port at which TxLongPoll is listening. -# datatype: string -frontend_port: none -# The uri that should be a proxy to the TxLongPoll server. -uri: /+longpoll/ - [request_daily_builds] dbuser: request-daily-builds === modified file 'lib/lp/services/configure.zcml' --- lib/lp/services/configure.zcml 2018-03-16 14:50:01 +0000 +++ lib/lp/services/configure.zcml 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ -<!-- Copyright 2010-2011 Canonical Ltd. This software is licensed under the +<!-- Copyright 2010-2019 Canonical Ltd. This software is licensed under the GNU Affero General Public License version 3 (see the file LICENSE). --> @@ -16,7 +16,6 @@ <include package=".inlinehelp" file="meta.zcml" /> <include package=".job" /> <include package=".librarian" /> - <include package=".longpoll" /> <include package=".mail" /> <include package=".memcache" /> <include package=".messages" /> === modified file 'lib/lp/services/features/flags.py' --- lib/lp/services/features/flags.py 2018-05-21 20:30:16 +0000 +++ lib/lp/services/features/flags.py 2019-04-16 14:35:10 +0000 @@ -1,4 +1,4 @@ -# Copyright 2010-2018 Canonical Ltd. This software is licensed under the +# Copyright 2010-2019 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). __all__ = [ @@ -171,13 +171,6 @@ 'None are enabled', '', ''), - ('longpoll.merge_proposals.enabled', - 'boolean', - ('Enables the longpoll mechanism for merge proposals so that diffs, ' - 'for example, are updated in-page when they are ready.'), - '', - '', - ''), ('ajax.batch_navigator.enabled', 'boolean', ('If true, batch navigators which have been wired to do so use ajax ' === removed directory 'lib/lp/services/longpoll' === removed file 'lib/lp/services/longpoll/__init__.py' === removed directory 'lib/lp/services/longpoll/adapters' === removed file 'lib/lp/services/longpoll/adapters/__init__.py' === removed file 'lib/lp/services/longpoll/adapters/event.py' --- lib/lp/services/longpoll/adapters/event.py 2015-07-09 12:18:51 +0000 +++ lib/lp/services/longpoll/adapters/event.py 1970-01-01 00:00:00 +0000 @@ -1,71 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long poll adapters.""" - -__metaclass__ = type -__all__ = [ - "generate_event_key", - "LongPollEvent", - ] - -from zope.component import getUtility - -from lp.services.messaging.interfaces import IMessageSession - - -def router_factory(event_key): - """Get a router for the given `event_key`.""" - return getUtility(IMessageSession).getProducer(event_key) - - -def generate_event_key(*components): - """Generate a suitable event name.""" - if len(components) == 0: - raise AssertionError( - "Event keys must contain at least one component.") - return "longpoll.event.%s" % ".".join( - str(component) for component in components) - - -class LongPollEvent: - """Base-class for event adapters. - - Sub-classes need to define the `event_key` property and declare something - along the lines of:: - - @adapter(IAwesomeThing) - @implementer(ILongPollEvent) - class LongPollAwesomeThingEvent(LongPollEvent): - ... - - Alternatively, use the `long_poll_event` class decorator:: - - @long_poll_event(IAwesomeThing) - class LongPollAwesomeThingEvent(LongPollEvent): - ... - - In both cases the adapter should be registered in a `configure.zcml` - somewhere sensible:: - - <adapter factory=".adapters.LongPollAwesomeThingEvent" /> - - """ - - def __init__(self, source): - self.source = source - - @property - def event_key(self): - """See `ILongPollEvent`.""" - raise NotImplementedError(self.__class__.event_key) - - def emit(self, **data): - """See `ILongPollEvent`. - - The data will be updated with `event_key`, a copy of `self.event_key`. - """ - event_key = self.event_key - data.update(event_key=event_key) - router = router_factory(event_key) - router.send(data) === removed file 'lib/lp/services/longpoll/adapters/storm.py' --- lib/lp/services/longpoll/adapters/storm.py 2011-10-05 15:46:30 +0000 +++ lib/lp/services/longpoll/adapters/storm.py 1970-01-01 00:00:00 +0000 @@ -1,116 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll life-cycle adapters.""" - -from __future__ import absolute_import - -__metaclass__ = type -__all__ = [] - -from lazr.lifecycle.interfaces import ( - IObjectCreatedEvent, - IObjectDeletedEvent, - IObjectModifiedEvent, - ) -from storm.base import Storm -from storm.info import ( - get_cls_info, - get_obj_info, - ) -from zope.component import adapter -from zope.interface.interfaces import IAttribute -from zope.security.proxy import removeSecurityProxy - -from lp.services.longpoll.adapters.event import ( - generate_event_key, - LongPollEvent, - ) -from lp.services.longpoll.interfaces import ( - ILongPollEvent, - long_poll_event, - ) - - -def gen_primary_key(model_instance): - """Generate the primary key values for the given model instance.""" - cls_info = get_obj_info(model_instance).cls_info - for primary_key_column in cls_info.primary_key: - yield primary_key_column.__get__(model_instance) - - -def get_primary_key(model_instance): - """Return the primary key for the given model instance. - - If the primary key contains only one value it is returned, otherwise all - the primary key values are returned in a tuple. - """ - pkey = tuple(gen_primary_key(model_instance)) - return pkey[0] if len(pkey) == 1 else pkey - - -@long_poll_event(Storm) -class LongPollStormEvent(LongPollEvent): - """A `ILongPollEvent` for events of `Storm` objects. - - This class knows how to construct a stable event key given a Storm object. - """ - - @property - def event_key(self): - """See `ILongPollEvent`. - - Constructs the key from the table name and primary key values of the - Storm model object. - """ - cls_info = get_obj_info(self.source).cls_info - return generate_event_key( - cls_info.table.name.lower(), - *gen_primary_key(self.source)) - - -@long_poll_event(type(Storm)) -class LongPollStormCreationEvent(LongPollEvent): - """A `ILongPollEvent` for events of `Storm` *classes*. - - This class knows how to construct a stable event key given a Storm class. - """ - - @property - def event_key(self): - """See `ILongPollEvent`. - - Constructs the key from the table name of the Storm class. - """ - cls_info = get_cls_info(self.source) - return generate_event_key( - cls_info.table.name.lower()) - - -@adapter(Storm, IObjectCreatedEvent) -def object_created(model_instance, object_event): - """Subscription handler for `Storm` creation events.""" - model_class = removeSecurityProxy(model_instance).__class__ - event = ILongPollEvent(model_class) - event.emit(what="created", id=get_primary_key(model_instance)) - - -@adapter(Storm, IObjectDeletedEvent) -def object_deleted(model_instance, object_event): - """Subscription handler for `Storm` deletion events.""" - event = ILongPollEvent(model_instance) - event.emit(what="deleted", id=get_primary_key(model_instance)) - - -@adapter(Storm, IObjectModifiedEvent) -def object_modified(model_instance, object_event): - """Subscription handler for `Storm` modification events.""" - edited_fields = object_event.edited_fields - if edited_fields is not None and len(edited_fields) != 0: - edited_field_names = sorted( - (field.__name__ if IAttribute.providedBy(field) else field) - for field in edited_fields) - event = ILongPollEvent(model_instance) - event.emit( - what="modified", edited_fields=edited_field_names, - id=get_primary_key(model_instance)) === removed file 'lib/lp/services/longpoll/adapters/subscriber.py' --- lib/lp/services/longpoll/adapters/subscriber.py 2015-07-09 12:18:51 +0000 +++ lib/lp/services/longpoll/adapters/subscriber.py 1970-01-01 00:00:00 +0000 @@ -1,58 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long poll adapters.""" - -__metaclass__ = type -__all__ = [ - "generate_subscribe_key", - "LongPollApplicationRequestSubscriber", - ] - -from uuid import uuid4 - -from lazr.restful.interfaces import IJSONRequestCache -from zope.component import ( - adapter, - getUtility, - ) -from zope.interface import implementer -from zope.publisher.interfaces import IApplicationRequest - -from lp.services.config import config -from lp.services.longpoll.interfaces import ILongPollSubscriber -from lp.services.messaging.interfaces import IMessageSession - - -def generate_subscribe_key(): - """Generate a suitable new, unique, subscribe key.""" - return "longpoll.subscribe.%s" % uuid4() - - -@adapter(IApplicationRequest) -@implementer(ILongPollSubscriber) -class LongPollApplicationRequestSubscriber: - - def __init__(self, request): - self.request = request - - @property - def subscribe_key(self): - objects = IJSONRequestCache(self.request).objects - if "longpoll" in objects: - return objects["longpoll"]["key"] - return None - - def subscribe(self, event): - cache = IJSONRequestCache(self.request) - if "longpoll" not in cache.objects: - cache.objects["longpoll"] = { - "uri": config.txlongpoll.uri, - "key": generate_subscribe_key(), - "subscriptions": [], - } - session = getUtility(IMessageSession) - subscribe_queue = session.getConsumer(self.subscribe_key) - producer = session.getProducer(event.event_key) - producer.associateConsumer(subscribe_queue) - cache.objects["longpoll"]["subscriptions"].append(event.event_key) === removed directory 'lib/lp/services/longpoll/adapters/tests' === removed file 'lib/lp/services/longpoll/adapters/tests/__init__.py' === removed file 'lib/lp/services/longpoll/adapters/tests/test_event.py' --- lib/lp/services/longpoll/adapters/tests/test_event.py 2015-07-08 16:05:11 +0000 +++ lib/lp/services/longpoll/adapters/tests/test_event.py 1970-01-01 00:00:00 +0000 @@ -1,78 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll event adapter tests.""" - -__metaclass__ = type - -from zope.interface import implementer - -from lp.services.longpoll.adapters.event import ( - generate_event_key, - LongPollEvent, - ) -from lp.services.longpoll.interfaces import ILongPollEvent -from lp.services.longpoll.testing import ( - capture_longpoll_emissions, - LongPollEventRecord, - ) -from lp.testing import TestCase -from lp.testing.layers import LaunchpadFunctionalLayer -from lp.testing.matchers import Contains - - -@implementer(ILongPollEvent) -class FakeEvent(LongPollEvent): - - @property - def event_key(self): - return "event-key-%s" % self.source - - -class TestLongPollEvent(TestCase): - - layer = LaunchpadFunctionalLayer - - def test_interface(self): - event = FakeEvent("source") - self.assertProvides(event, ILongPollEvent) - - def test_event_key(self): - # event_key is not implemented in LongPollEvent; subclasses must - # provide it. - event = LongPollEvent("source") - self.assertRaises(NotImplementedError, getattr, event, "event_key") - - def test_emit(self): - # LongPollEvent.emit() sends the given data to `event_key`. - event = FakeEvent("source") - event_data = {"hello": 1234} - with capture_longpoll_emissions() as log: - event.emit(**event_data) - expected_message = LongPollEventRecord( - event_key=event.event_key, - data=dict(event_data, event_key=event.event_key)) - self.assertThat(log, Contains(expected_message)) - - -class TestFunctions(TestCase): - - def test_generate_event_key_no_components(self): - self.assertRaises( - AssertionError, generate_event_key) - - def test_generate_event_key(self): - self.assertEqual( - "longpoll.event.event-name", - generate_event_key("event-name")) - self.assertEqual( - "longpoll.event.source-name.event-name", - generate_event_key("source-name", "event-name")) - self.assertEqual( - "longpoll.event.type-name.source-name.event-name", - generate_event_key("type-name", "source-name", "event-name")) - - def test_generate_event_key_stringifies_components(self): - self.assertEqual( - "longpoll.event.job.1234.COMPLETED", - generate_event_key("job", 1234, "COMPLETED")) === removed file 'lib/lp/services/longpoll/adapters/tests/test_storm.py' --- lib/lp/services/longpoll/adapters/tests/test_storm.py 2012-01-01 02:58:52 +0000 +++ lib/lp/services/longpoll/adapters/tests/test_storm.py 1970-01-01 00:00:00 +0000 @@ -1,170 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll event adapter tests.""" - -__metaclass__ = type - -from lazr.lifecycle.event import ( - ObjectCreatedEvent, - ObjectDeletedEvent, - ObjectModifiedEvent, - ) -from storm.base import Storm -from storm.properties import Int -from zope.event import notify -from zope.interface import Attribute - -from lp.services.longpoll.adapters.storm import ( - gen_primary_key, - get_primary_key, - ) -from lp.services.longpoll.interfaces import ILongPollEvent -from lp.services.longpoll.testing import ( - capture_longpoll_emissions, - LongPollEventRecord, - ) -from lp.testing import TestCase -from lp.testing.layers import LaunchpadFunctionalLayer -from lp.testing.matchers import Provides - - -class FakeStormClass(Storm): - - __storm_table__ = 'FakeTable' - - id = Int(primary=True) - - -class FakeStormCompoundPrimaryKeyClass(Storm): - - __storm_table__ = 'FakeTableWithCompoundPrimaryKey' - __storm_primary__ = 'id1', 'id2' - - id1 = Int() - id2 = Int() - - -class TestFunctions(TestCase): - - def test_gen_primary_key(self): - # gen_primary_key() returns an iterable of values from the model - # instance's primary key. - storm_object = FakeStormClass() - storm_object.id = 1234 - self.assertEqual([1234], list(gen_primary_key(storm_object))) - - def test_gen_primary_key_compound_key(self): - # gen_primary_key() returns an iterable of values from the model - # instance's primary key. - storm_object = FakeStormCompoundPrimaryKeyClass() - storm_object.id1 = 1234 - storm_object.id2 = 5678 - self.assertEqual([1234, 5678], list(gen_primary_key(storm_object))) - - def test_get_primary_key(self): - # get_primary_key() returns the value of the model instance's primary - # key. - storm_object = FakeStormClass() - storm_object.id = 1234 - self.assertEqual(1234, get_primary_key(storm_object)) - - def test_get_primary_key_compound_key(self): - # get_primary_key() returns a tuple of all the values in the model - # instance's primary key when the model uses a compound primary key. - storm_object = FakeStormCompoundPrimaryKeyClass() - storm_object.id1 = 1234 - storm_object.id2 = 5678 - self.assertEqual((1234, 5678), get_primary_key(storm_object)) - - -class TestStormLifecycle(TestCase): - - layer = LaunchpadFunctionalLayer - - def test_storm_event_adapter(self): - storm_object = FakeStormClass() - storm_object.id = 1234 - event = ILongPollEvent(storm_object) - self.assertThat(event, Provides(ILongPollEvent)) - self.assertEqual( - "longpoll.event.faketable.1234", - event.event_key) - - def test_storm_creation_event_adapter(self): - event = ILongPollEvent(FakeStormClass) - self.assertThat(event, Provides(ILongPollEvent)) - self.assertEqual( - "longpoll.event.faketable", - event.event_key) - - def test_storm_object_created(self): - storm_object = FakeStormClass() - storm_object.id = 1234 - with capture_longpoll_emissions() as log: - notify(ObjectCreatedEvent(storm_object)) - expected = LongPollEventRecord( - "longpoll.event.faketable", { - "event_key": "longpoll.event.faketable", - "what": "created", - "id": 1234, - }) - self.assertEqual([expected], log) - - def test_storm_object_deleted(self): - storm_object = FakeStormClass() - storm_object.id = 1234 - with capture_longpoll_emissions() as log: - notify(ObjectDeletedEvent(storm_object)) - expected = LongPollEventRecord( - "longpoll.event.faketable.1234", { - "event_key": "longpoll.event.faketable.1234", - "what": "deleted", - "id": 1234, - }) - self.assertEqual([expected], log) - - def test_storm_object_modified(self): - storm_object = FakeStormClass() - storm_object.id = 1234 - with capture_longpoll_emissions() as log: - object_event = ObjectModifiedEvent( - storm_object, storm_object, ("itchy", "scratchy")) - notify(object_event) - expected = LongPollEventRecord( - "longpoll.event.faketable.1234", { - "event_key": "longpoll.event.faketable.1234", - "what": "modified", - "edited_fields": ["itchy", "scratchy"], - "id": 1234, - }) - self.assertEqual([expected], log) - - def test_storm_object_modified_no_edited_fields(self): - # A longpoll event is not emitted unless edited_fields is populated. - storm_object = FakeStormClass() - storm_object.id = 1234 - with capture_longpoll_emissions() as log: - notify(ObjectModifiedEvent(storm_object, storm_object, None)) - self.assertEqual([], log) - with capture_longpoll_emissions() as log: - notify(ObjectModifiedEvent(storm_object, storm_object, ())) - self.assertEqual([], log) - - def test_storm_object_modified_edited_fields_are_zope_attributes(self): - # The names of IAttribute fields in edited_fields are used in the - # longpoll event. - storm_object = FakeStormClass() - storm_object.id = 1234 - with capture_longpoll_emissions() as log: - object_event = ObjectModifiedEvent( - storm_object, storm_object, ("foo", Attribute("bar"))) - notify(object_event) - expected = LongPollEventRecord( - "longpoll.event.faketable.1234", { - "event_key": "longpoll.event.faketable.1234", - "what": "modified", - "edited_fields": ["bar", "foo"], - "id": 1234, - }) - self.assertEqual([expected], log) === removed file 'lib/lp/services/longpoll/adapters/tests/test_subscriber.py' --- lib/lp/services/longpoll/adapters/tests/test_subscriber.py 2015-07-08 16:05:11 +0000 +++ lib/lp/services/longpoll/adapters/tests/test_subscriber.py 1970-01-01 00:00:00 +0000 @@ -1,137 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll subscriber adapter tests.""" - -__metaclass__ = type - -from itertools import count - -from lazr.restful.interfaces import IJSONRequestCache -from testtools.matchers import ( - Not, - StartsWith, - ) -from zope.component import getUtility -from zope.interface import implementer - -from lp.services.longpoll.adapters.subscriber import ( - generate_subscribe_key, - LongPollApplicationRequestSubscriber, - ) -from lp.services.longpoll.interfaces import ( - ILongPollEvent, - ILongPollSubscriber, - ) -from lp.services.messaging.interfaces import IMessageSession -from lp.services.webapp.servers import LaunchpadTestRequest -from lp.testing import TestCase -from lp.testing.layers import LaunchpadFunctionalLayer -from lp.testing.matchers import Contains - - -@implementer(ILongPollEvent) -class FakeEvent: - - event_key_indexes = count(1) - - def __init__(self): - self.event_key = "event-key-%d" % next(self.event_key_indexes) - - -class TestLongPollSubscriber(TestCase): - - layer = LaunchpadFunctionalLayer - - def test_interface(self): - request = LaunchpadTestRequest() - subscriber = LongPollApplicationRequestSubscriber(request) - self.assertProvides(subscriber, ILongPollSubscriber) - - def test_subscribe_key(self): - request = LaunchpadTestRequest() - subscriber = LongPollApplicationRequestSubscriber(request) - # A subscribe key is not generated yet. - self.assertIs(subscriber.subscribe_key, None) - # It it only generated on the first subscription. - subscriber.subscribe(FakeEvent()) - subscribe_key = subscriber.subscribe_key - self.assertIsInstance(subscribe_key, str) - self.assertNotEqual(0, len(subscribe_key)) - # It remains the same for later subscriptions. - subscriber.subscribe(FakeEvent()) - self.assertEqual(subscribe_key, subscriber.subscribe_key) - - def test_adapter(self): - request = LaunchpadTestRequest() - subscriber = ILongPollSubscriber(request) - self.assertIsInstance( - subscriber, LongPollApplicationRequestSubscriber) - # A difference subscriber is returned on subsequent adaptions, but it - # has the same subscribe_key. - subscriber2 = ILongPollSubscriber(request) - self.assertIsNot(subscriber, subscriber2) - self.assertEqual(subscriber.subscribe_key, subscriber2.subscribe_key) - - def test_subscribe_queue(self): - # LongPollApplicationRequestSubscriber.subscribe() creates a new queue - # with a new unique name that is bound to the event's event_key. - request = LaunchpadTestRequest() - event = FakeEvent() - subscriber = ILongPollSubscriber(request) - subscriber.subscribe(event) - message = '{"hello": 1234}' - session = getUtility(IMessageSession) - routing_key = session.getProducer(event.event_key) - routing_key.send(message) - session.flush() - subscribe_queue = session.getConsumer(subscriber.subscribe_key) - self.assertEqual( - message, subscribe_queue.receive(timeout=5)) - - def test_json_cache_not_populated_on_init(self): - # LongPollApplicationRequestSubscriber does not put the name of the - # new queue into the JSON cache. - request = LaunchpadTestRequest() - cache = IJSONRequestCache(request) - self.assertThat(cache.objects, Not(Contains("longpoll"))) - ILongPollSubscriber(request) - self.assertThat(cache.objects, Not(Contains("longpoll"))) - - def test_longpoll_uri_config(self): - # The JSON cache contains config.txlongpoll.uri. - self.pushConfig("txlongpoll", uri="/+longpoll/") - request = LaunchpadTestRequest() - cache = IJSONRequestCache(request) - ILongPollSubscriber(request).subscribe(FakeEvent()) - self.assertEqual('/+longpoll/', cache.objects["longpoll"]["uri"]) - - def test_json_cache_populated_on_subscribe(self): - # To aid with debugging the event_key of subscriptions are added to - # the JSON cache. - request = LaunchpadTestRequest() - cache = IJSONRequestCache(request) - event1 = FakeEvent() - ILongPollSubscriber(request).subscribe(event1) # Side-effects! - self.assertThat(cache.objects, Contains("longpoll")) - self.assertThat(cache.objects["longpoll"], Contains("key")) - self.assertThat(cache.objects["longpoll"], Contains("subscriptions")) - self.assertEqual( - [event1.event_key], - cache.objects["longpoll"]["subscriptions"]) - # More events can be subscribed. - event2 = FakeEvent() - ILongPollSubscriber(request).subscribe(event2) - self.assertEqual( - [event1.event_key, event2.event_key], - cache.objects["longpoll"]["subscriptions"]) - - -class TestFunctions(TestCase): - - def test_generate_subscribe_key(self): - subscribe_key = generate_subscribe_key() - expected_prefix = "longpoll.subscribe." - self.assertThat(subscribe_key, StartsWith(expected_prefix)) - # The key contains a 36 character UUID. - self.assertEqual(len(expected_prefix) + 36, len(subscribe_key)) === removed file 'lib/lp/services/longpoll/configure.zcml' --- lib/lp/services/longpoll/configure.zcml 2011-10-05 15:14:53 +0000 +++ lib/lp/services/longpoll/configure.zcml 1970-01-01 00:00:00 +0000 @@ -1,15 +0,0 @@ -<!-- Copyright 2011 Canonical Ltd. This software is licensed under the - GNU Affero General Public License version 3 (see the file LICENSE). ---> -<configure - xmlns="http://namespaces.zope.org/zope" - xmlns:browser="http://namespaces.zope.org/browser" - xmlns:i18n="http://namespaces.zope.org/i18n" - i18n_domain="launchpad"> - <adapter factory=".adapters.storm.LongPollStormEvent" /> - <adapter factory=".adapters.storm.LongPollStormCreationEvent" /> - <adapter factory=".adapters.subscriber.LongPollApplicationRequestSubscriber" /> - <subscriber handler=".adapters.storm.object_created" /> - <subscriber handler=".adapters.storm.object_deleted" /> - <subscriber handler=".adapters.storm.object_modified" /> -</configure> === removed file 'lib/lp/services/longpoll/interfaces.py' --- lib/lp/services/longpoll/interfaces.py 2011-09-20 19:04:19 +0000 +++ lib/lp/services/longpoll/interfaces.py 1970-01-01 00:00:00 +0000 @@ -1,64 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll infrastructure interfaces.""" - -__metaclass__ = type -__all__ = [ - "ILongPollEvent", - "ILongPollSubscriber", - "long_poll_event", - ] - -from zope.component import adapter -from zope.interface import ( - Attribute, - classImplements, - Interface, - ) - - -class ILongPollEvent(Interface): - - source = Attribute("The event source.") - - event_key = Attribute( - "The key with which events will be emitted. Should be predictable " - "and stable.") - - def emit(**data): - """Emit the given data to `event_key`. - - :param data: Any data structures that can be dumped as JSON. - """ - - -class ILongPollSubscriber(Interface): - - subscribe_key = Attribute( - "The key which the subscriber must know in order to be able " - "to long-poll for subscribed events. Should be infeasible to " - "guess, a UUID for example.") - - def subscribe(event): - """Subscribe to the given event. - - :type event: ILongPollEvent - """ - - -def long_poll_event(source_spec): - """Class decorator to declare an `ILongPollEvent`. - - :param source_spec: An interface or other specification understood by - `zope.component` (a plain class can be passed too) that defines the - source of an event. `IJob` or `storm.base.Storm` for example. - """ - declare_adapter = adapter(source_spec) - - def declare_event(cls): - classImplements(cls, ILongPollEvent) - declare_adapter(cls) - return cls - - return declare_event === removed file 'lib/lp/services/longpoll/testing.py' --- lib/lp/services/longpoll/testing.py 2011-09-23 16:36:56 +0000 +++ lib/lp/services/longpoll/testing.py 1970-01-01 00:00:00 +0000 @@ -1,57 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Things that help with testing of longpoll.""" - -__metaclass__ = type -__all__ = [ - "capture_longpoll_emissions", - "LongPollEventRecord", - ] - -from collections import namedtuple -from contextlib import contextmanager -from functools import partial - -from lp.services.longpoll.adapters import event - - -LongPollEventRecord = namedtuple( - "LongPollEventRecord", ("event_key", "data")) - - -class LoggingRouter: - """A test double for `IMessageProducer`. - - Saves messages as `LongPollEventRecord` tuples to a log. - - :param log: A callable accepting a single `LongPollEventRecord`. - :param routing_key: See `IMessageSession.getProducer`. - """ - - def __init__(self, log, routing_key): - self.log = log - self.routing_key = routing_key - - def send(self, data): - record = LongPollEventRecord(self.routing_key, data) - self.log(record) - - -@contextmanager -def capture_longpoll_emissions(): - """Capture longpoll emissions while this context is in force. - - This returns a list in which `LongPollEventRecord` tuples will be - recorded, in the order they're emitted. - - Note that normal event emission is *suppressed globally* while this - context is in force; *all* events will be stored in the log. - """ - log = [] - original_router_factory = event.router_factory - event.router_factory = partial(LoggingRouter, log.append) - try: - yield log - finally: - event.router_factory = original_router_factory === removed directory 'lib/lp/services/longpoll/tests' === removed file 'lib/lp/services/longpoll/tests/__init__.py' === removed file 'lib/lp/services/longpoll/tests/test_interfaces.py' --- lib/lp/services/longpoll/tests/test_interfaces.py 2011-09-20 19:04:19 +0000 +++ lib/lp/services/longpoll/tests/test_interfaces.py 1970-01-01 00:00:00 +0000 @@ -1,33 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Long-poll interface tests.""" - -__metaclass__ = type - -from zope.component import adaptedBy -from zope.interface import Interface - -from lp.services.longpoll.interfaces import ( - ILongPollEvent, - long_poll_event, - ) -from lp.testing import TestCase - - -class IEventSourceInterface(Interface): - """Test interface for an event source.""" - - -class TestLongPollInterfaces(TestCase): - - def test_long_poll_event(self): - # long_poll_event is a class decorator that declares a class as an - # ILongPollEvent. - @long_poll_event(IEventSourceInterface) - class Something: - """An example event source.""" - self.assertTrue(ILongPollEvent.implementedBy(Something)) - self.assertEqual( - (IEventSourceInterface,), - adaptedBy(Something)) === removed directory 'lib/lp/services/txlongpoll' === removed file 'lib/lp/services/txlongpoll/__init__.py' === removed file 'lib/lp/services/txlongpoll/server.py' --- lib/lp/services/txlongpoll/server.py 2011-09-30 07:28:45 +0000 +++ lib/lp/services/txlongpoll/server.py 1970-01-01 00:00:00 +0000 @@ -1,31 +0,0 @@ -# Copyright 2011 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""TxLongPoll server fixture.""" - -__metaclass__ = type -__all__ = [ - 'TxLongPollServer', - ] - -from textwrap import dedent - -from txlongpollfixture.server import TxLongPollFixture - - -class TxLongPollServer(TxLongPollFixture): - """A TxLongPoll server fixture with Launchpad-specific config. - - :ivar service_config: A snippet of .ini that describes the `txlongpoll` - configuration. - """ - - def setUp(self): - super(TxLongPollServer, self).setUp() - setattr( - self, 'service_config', - dedent("""\ - [txlongpoll] - frontend_port: %d - """ % ( - self.config.frontend_port))) === removed directory 'lib/lp/services/txlongpoll/tests' === removed file 'lib/lp/services/txlongpoll/tests/__init__.py' === removed file 'lib/lp/services/txlongpoll/tests/test_server.py' --- lib/lp/services/txlongpoll/tests/test_server.py 2017-09-23 03:13:41 +0000 +++ lib/lp/services/txlongpoll/tests/test_server.py 1970-01-01 00:00:00 +0000 @@ -1,38 +0,0 @@ -# Copyright 2011-2017 Canonical Ltd. This software is licensed under the -# GNU Affero General Public License version 3 (see the file LICENSE). - -"""Tests for lp.services.rabbit.TxLongPollServer.""" - -__metaclass__ = type - -from ConfigParser import SafeConfigParser -import os -from StringIO import StringIO - -from lp.services.config import config -from lp.services.txlongpoll.server import TxLongPollServer -from lp.testing import TestCase -from lp.testing.layers import RabbitMQLayer - - -class TestTxLongPollServer(TestCase): - - layer = RabbitMQLayer - - def test_service_config(self): - # TxLongPollServer pokes some .ini configuration into its - # service_config attributes. - twistd_bin = os.path.join(config.root, 'bin', 'twistd') - fixture = self.useFixture(TxLongPollServer( - broker_user='guest', broker_password='guest', broker_vhost='/', - broker_port=123, frontend_port=None, - twistd_bin=twistd_bin)) - service_config = SafeConfigParser() - service_config.readfp(StringIO(getattr(fixture, 'service_config'))) - self.assertEqual(["txlongpoll"], service_config.sections()) - # txlongpoll section - expected = { - "frontend_port": "%d" % fixture.config.frontend_port, - } - observed = dict(service_config.items("txlongpoll")) - self.assertEqual(expected, observed) === modified file 'setup.py' --- setup.py 2018-07-16 10:51:04 +0000 +++ setup.py 2019-04-16 14:35:10 +0000 @@ -227,8 +227,6 @@ 'treq', 'Twisted[conch,tls]', 'txfixtures', - 'txlongpoll', - 'txlongpollfixture', 'txpkgupload', 'virtualenv-tools3', 'wadllib',
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : [email protected] Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp

