launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04400
[Merge] lp:~abentley/launchpad/reload-cache into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/reload-cache into lp:launchpad with lp:~abentley/launchpad/getnewcache as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~abentley/launchpad/reload-cache/+merge/69527
= Summary =
Support loading a new copy of the LP.cache.
== Proposed fix ==
See above.
== Pre-implementation notes ==
== Implementation details ==
This builds on ~bac/launchpad/getnewcache, which provides a ++model++ namespace
similar to the ++form++ namespace. The ++model++ namespace provides the view's
IJSONRequestCache as JSON, serialized exactly how it would be serialized in the
page.
This branch merges some last-minute changes I made to json-serialization. To
ensure the correct diff is shown, I've merged those changes into
~abentley/launchpad/getnewcache and used it, rather than bac's version, as the
prerequisite branch.
The new method is lp.client.load_model(). To support it, I extracted
get_view_url and get_form_url. It takes an optional 'io' config parameter,
which makes testing easier.
The IJSONRequestCache serialization supports putting plain Python object types
in the cache, so I updated the deserialization, 'wrap_resource', to work the
same way. This made convert_cache redundant, so I removed it.
To test load_model, I implemented IORecorder, which provides a mock version of
Y.io. It serves the same purpose as MockIO, but has an imperative programming
style and supports multiple concurrent requests. I moved MockHttpResponse into
IORecorder's file.
== Tests ==
bin/test -t test_lp_client
== Demo and Q/A ==
None
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/app/javascript/tests/test_lp_client.js
lib/lp/soyuz/browser/distroseriessourcepackagerelease.py
lib/lp/registry/browser/sourcepackage.py
lib/lp/registry/browser/peoplemerge.py
lib/canonical/launchpad/webapp/publisher.py
lib/canonical/launchpad/webapp/templates/launchpad-model.pt
lib/lp/code/browser/bazaar.py
lib/lp/registry/browser/codeofconduct.py
lib/lp/soyuz/browser/sourcepackage.py
lib/lp/registry/browser/distributionsourcepackage.py
lib/canonical/launchpad/webapp/tests/test_publisher.py
lib/lp/app/javascript/client.js
lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js
lib/lp/app/javascript/tests/test_lp_client.html
lib/lp/soyuz/browser/distroseriesbinarypackage.py
lib/lp/app/templates/base-layout-macros.pt
lib/lp/testing/__init__.py
lib/lp/bugs/browser/configure.zcml
lib/canonical/launchpad/webapp/tests/test_view_model.py
lib/lp/bugs/browser/cve.py
lib/lp/soyuz/browser/distroarchseriesbinarypackagerelease.py
lib/lp/soyuz/browser/distroarchseriesbinarypackage.py
lib/lp/translations/browser/tests/test_sharing_details.py
lib/canonical/launchpad/webapp/configure.zcml
lib/lp/translations/browser/translationgroup.py
lib/lp/app/webservice/tests/test_marshallers.py
lib/lp/blueprints/browser/configure.zcml
lib/lp/app/javascript/testing/iorecorder.js
lib/lp/translations/javascript/sourcepackage_sharing_details.js
lib/lp/registry/browser/teammembership.py
lib/lp/app/webservice/marshallers.py
lib/lp/translations/browser/translations.py
lib/canonical/launchpad/webapp/namespace.py
lib/lp/translations/browser/sourcepackage.py
lib/lp/registry/browser/tests/test_subscription_links.py
./lib/lp/translations/browser/tests/test_sharing_details.py
218: E251 no spaces around keyword / parameter equals
226: E251 no spaces around keyword / parameter equals
254: E251 no spaces around keyword / parameter equals
265: E251 no spaces around keyword / parameter equals
277: E251 no spaces around keyword / parameter equals
311: E251 no spaces around keyword / parameter equals
--
https://code.launchpad.net/~abentley/launchpad/reload-cache/+merge/69527
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/reload-cache into lp:launchpad.
=== modified file 'lib/lp/app/javascript/client.js'
--- lib/lp/app/javascript/client.js 2011-04-26 20:33:18 +0000
+++ lib/lp/app/javascript/client.js 2011-07-27 18:39:38 +0000
@@ -164,6 +164,50 @@
}
};
+
+/**
+ * Get the URL of the view for an Entry
+ * @method get_view_url
+ * @param {Entry} entry
+ * @param {String} view_name
+ * @param {String} namespace
+ * @return {String} URL
+ */
+module.get_view_url = function(entry, view_name, namespace){
+ entry_url = Y.lp.get_url_path(entry.get('web_link'));
+ return entry_url + '/' + view_name + '/++' + namespace + '++';
+};
+
+
+/**
+ * Get the URL of the form for a view for an Entry
+ * @method get_form_url
+ * @param {Entry} entry
+ * @param {String} view_name
+ * @return {String} URL
+ */
+module.get_form_url = function(entry, view_name) {
+ return module.get_view_url(entry, view_name, 'form');
+};
+
+
+module.load_model = function(entry, view_name, config){
+ var url = module.get_view_url(entry, view_name, 'model');
+ var old_on_success = config.on.success;
+ var on = config.on;
+ on.success = module.wrap_resource_on_success;
+ var y_config = {
+ on: on,
+ 'arguments': [entry.lp_client, url, old_on_success, false]
+ };
+ var io = config.io;
+ if (!Y.Lang.isValue(io)) {
+ io = Y.io;
+ }
+ io(url, y_config);
+};
+
+
module.add_accept = function(config, headers) {
if (headers === undefined) {
headers = {};
@@ -702,6 +746,8 @@
},
'wrap_resource': function(uri, representation) {
+ var key;
+ var new_representation;
/* Given a representation, turn it into a subclass of Resource. */
if (representation === null || representation === undefined) {
return representation;
@@ -717,6 +763,25 @@
// It's a list. Treat it as a collection;
// it should be slicable.
return new Collection(this, representation, uri);
+ } else if (Y.Lang.isObject(representation)) {
+ // It's an Array or mapping. Recurse into it.
+ if (Y.Lang.isArray(representation)) {
+ new_representation = [];
+ }
+ else {
+ new_representation = {};
+ }
+ for (key in representation) {
+ if (representation.hasOwnProperty(key)) {
+ var value = representation[key];
+ if (Y.Lang.isValue(value)) {
+ value = this.wrap_resource(
+ value.self_link, value);
+ }
+ new_representation[key] = value;
+ }
+ }
+ return new_representation;
} else {
// It's a random JSON object. Leave it alone.
return representation;
@@ -844,7 +909,8 @@
module.FormErrorHandler = FormErrorHandler;
-}, "0.1" ,{"requires":["attribute", "io", "json-parse", "json-stringify"]});
+}, "0.1",
+ {"requires":["attribute", "io", "json-parse", "json-stringify", "lp"]});
YUI.add('lp.client.plugins', function (Y) {
=== added file 'lib/lp/app/javascript/testing/iorecorder.js'
--- lib/lp/app/javascript/testing/iorecorder.js 1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/testing/iorecorder.js 2011-07-27 18:39:38 +0000
@@ -0,0 +1,47 @@
+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
+
+YUI.add('lp.testing.iorecorder', function(Y) {
+ namespace = Y.namespace("lp.testing.iorecorder");
+ function MockHttpResponse () {
+ this.responseText = '[]';
+ this.responseHeaders = {};
+ }
+
+ MockHttpResponse.prototype = {
+ setResponseHeader: function (header, value) {
+ this.responseHeaders[header] = value;
+ },
+
+ getResponseHeader: function(header) {
+ return this.responseHeaders[header];
+ }
+ };
+ namespace.MockHttpResponse = MockHttpResponse;
+
+
+ function IORecorderRequest(url, config){
+ this.url = url;
+ this.config = config;
+ this.response = null;
+ }
+
+
+ IORecorderRequest.prototype.success = function(value, headers){
+ this.response = new MockHttpResponse();
+ this.response.setResponseHeader(
+ 'Content-Type', headers['Content-Type']);
+ this.response.responseText = value;
+ this.config.on.success(null, this.response, this.config['arguments']);
+ };
+
+
+ function IORecorder(){
+ this.requests = [];
+ }
+
+
+ IORecorder.prototype.do_io = function(url, config){
+ this.requests.push(new IORecorderRequest(url, config));
+ };
+ namespace.IORecorder = IORecorder;
+}, '0.1', {});
=== modified file 'lib/lp/app/javascript/tests/test_lp_client.html'
--- lib/lp/app/javascript/tests/test_lp_client.html 2011-07-08 06:06:15 +0000
+++ lib/lp/app/javascript/tests/test_lp_client.html 2011-07-27 18:39:38 +0000
@@ -1,7 +1,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
- <title>Launchpad lp.client moduel</title>
+ <title>Launchpad lp.client module</title>
<!-- YUI and test setup -->
<script type="text/javascript"
@@ -10,8 +10,11 @@
<link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
<script type="text/javascript"
src="../../../app/javascript/testing/testrunner.js"></script>
+ <script type="text/javascript"
+ src="../../../app/javascript/testing/iorecorder.js"></script>
<!-- The module under test -->
+ <script type="text/javascript" src="../lp.js"></script>
<script type="text/javascript" src="../client.js"></script>
<!-- The test suite -->
=== modified file 'lib/lp/app/javascript/tests/test_lp_client.js'
--- lib/lp/app/javascript/tests/test_lp_client.js 2011-07-08 05:12:39 +0000
+++ lib/lp/app/javascript/tests/test_lp_client.js 2011-07-27 18:39:38 +0000
@@ -1,7 +1,7 @@
/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
-YUI().use('lp.testing.runner', 'test', 'console', 'lp.client',
- 'escape', function(Y) {
+YUI().use('lp.testing.runner', 'lp.testing.iorecorder', 'test', 'console',
+ 'lp.client', 'escape', function(Y) {
var Assert = Y.Assert; // For easy access to isTrue(), etc.
@@ -58,6 +58,77 @@
Assert.areEqual(
get_field_uri("/has/slash/", "field"),
"/api/devel/has/slash/field");
+ },
+ test_view_url: function() {
+ entry_repr = {web_link: 'http://example.com/context'};
+ var context = new Y.lp.client.Entry(null, entry_repr, null);
+ expected = '/context/+myview/++mynamespace++';
+ actual = Y.lp.client.get_view_url(context, '+myview', 'mynamespace');
+ Assert.areEqual(expected, actual);
+ },
+ test_get_form_url: function() {
+ entry_repr = {web_link: 'http://example.com/context'};
+ var context = new Y.lp.client.Entry(null, entry_repr, null);
+ expected = '/context/+myview/++form++';
+ actual = Y.lp.client.get_form_url(context, '+myview');
+ Assert.areEqual(expected, actual);
+ },
+ test_load_model: function(){
+ var recorder = new Y.lp.testing.iorecorder.IORecorder();
+ Assert.areEqual(0, recorder.requests.length);
+ var mylist = [];
+ var config = {
+ io: Y.bind(recorder.do_io, recorder),
+ on: {
+ success: Y.bind(mylist.push, mylist)
+ }
+ };
+ var entry_repr = {web_link: 'http://example.com/context'};
+ var client = new Y.lp.client.Launchpad();
+ var context = new Y.lp.client.Entry(client, entry_repr, null);
+ Y.lp.client.load_model(context, '+myview', config);
+ var request = recorder.requests[0];
+ Assert.areEqual('/context/+myview/++model++', request.url);
+ request.success(
+ '{"boolean": true, "entry": {"resource_type_link": "foo"}}',
+ {'Content-Type': 'application/json'});
+ var result = mylist[0];
+ Assert.areSame(true, result.boolean);
+ Assert.isInstanceOf(Y.lp.client.Entry, result.entry);
+ Assert.areSame('foo', result.entry.get('resource_type_link'));
+ },
+ test_wrap_resource_nested_mapping: function() {
+ var foo = {
+ baa: {},
+ bar: {
+ baz: {
+ resource_type_link: 'qux'}
+ }
+ };
+ foo = new Y.lp.client.Launchpad().wrap_resource(null, foo);
+ Y.Assert.isInstanceOf(Y.lp.client.Entry, foo.bar.baz);
+ },
+ test_wrap_resource_nested_array: function() {
+ var foo = [[{resource_type_link: 'qux'}]];
+ foo = new Y.lp.client.Launchpad().wrap_resource(null, foo);
+ Y.Assert.isInstanceOf(Y.lp.client.Entry, foo[0][0]);
+ },
+ test_wrap_resource_creates_array: function() {
+ var foo = ['a'];
+ var bar = new Y.lp.client.Launchpad().wrap_resource(null, foo);
+ Y.Assert.areNotSame(foo, bar);
+ },
+ test_wrap_resource_creates_mapping: function() {
+ var foo = {a: 'b'};
+ var bar = new Y.lp.client.Launchpad().wrap_resource(null, foo);
+ Y.Assert.areNotSame(foo, bar);
+ },
+ test_wrap_resource_null: function() {
+ var foo = {
+ bar: null
+ };
+ foo = new Y.lp.client.Launchpad().wrap_resource(null, foo);
+ Y.Assert.isNull(foo.bar);
}
}));
@@ -208,28 +279,13 @@
}
}));
-function MockHttpResponse () {
- this.responseText = '[]';
- this.responseHeaders = {};
-}
-
-MockHttpResponse.prototype = {
- setResponseHeader: function (header, value) {
- this.responseHeaders[header] = value;
- },
-
- getResponseHeader: function(header) {
- return this.responseHeaders[header];
- }
-};
-
suite.add(new Y.Test.Case({
name: "lp.client.notifications",
setUp: function() {
this.client = new Y.lp.client.Launchpad();
this.args=[this.client, null, this._on_success, false];
- this.response = new MockHttpResponse();
+ this.response = new Y.lp.testing.iorecorder.MockHttpResponse();
this.response.setResponseHeader('Content-Type', 'application/json');
},
=== modified file 'lib/lp/translations/javascript/sourcepackage_sharing_details.js'
--- lib/lp/translations/javascript/sourcepackage_sharing_details.js 2011-04-22 21:33:30 +0000
+++ lib/lp/translations/javascript/sourcepackage_sharing_details.js 2011-07-27 18:39:38 +0000
@@ -83,21 +83,6 @@
});
namespace.LinkCheckItem = LinkCheckItem;
-namespace.convert_cache = function(lp_client, cache) {
- var new_cache = {};
- var value;
- var key;
- for (key in cache){
- if (cache.hasOwnProperty(key)){
- value = cache[key];
- if (value !== null){
- value = lp_client.wrap_resource(value.self_link, value);
- }
- new_cache[key] = value;
- }
- }
- return new_cache;
-};
namespace.autoimport_modes = {
@@ -174,14 +159,8 @@
}
-function form_url(entry, view_name) {
- entry_url = Y.lp.get_url_path(entry.get('web_link'));
- return entry_url + '/' + view_name + '/++form++';
-}
-
-
function update_form(overlay, entry, view_name) {
- var url = form_url(entry, view_name);
+ var url = Y.lp.client.get_form_url(entry, view_name);
overlay.loadFormContentAndRender(url);
overlay.render();
}
@@ -212,7 +191,7 @@
}
form_data_entries.push('field.actions.' + action + '=ignored');
var data = form_data_entries.join('&');
- var url = form_url(entry, view_name);
+ var url = Y.lp.client.get_form_url(entry, view_name);
config.method = 'POST';
config.data = data;
Y.io(url, config);
@@ -583,7 +562,7 @@
namespace.prepare = function(cache) {
var sharing_controller = new namespace.TranslationSharingController();
var lp_client = new Y.lp.client.Launchpad();
- cache = namespace.convert_cache(lp_client, cache);
+ cache = lp_client.wrap_resource(null, cache);
var branch_picker_config = {
picker_activator: '#branch-incomplete-picker a',
header : 'Select translation branch',
=== modified file 'lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js'
--- lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js 2011-07-08 06:21:11 +0000
+++ lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js 2011-07-27 18:39:38 +0000
@@ -133,7 +133,7 @@
};
var ctrl = new TranslationSharingController();
var lp_client = new Y.lp.client.Launchpad();
- cache = test_ns.convert_cache(lp_client, cache);
+ cache = lp_client.wrap_resource(null, cache);
var import_overlay = {
loadFormContentAndRender: function() {},
render: function() {},
@@ -159,21 +159,6 @@
tsconfig.get('translations_usage').get('complete'));
Y.Assert.areSame(ctrl.get('source_package'), cache.context);
},
- test_convert_cache: function() {
- var cache = {
- foo: {
- self_link: 'http://foo',
- resource_type_link: 'http://foo_type'
- },
- bar: null,
- baz: true
- };
- var lp_client = new Y.lp.client.Launchpad();
- cache = test_ns.convert_cache(lp_client, cache);
- Y.Assert.isNull(cache.bar);
- Y.Assert.isTrue(cache.baz);
- Y.Assert.areEqual('http://foo', cache.foo.get('self_link'));
- },
test_set_permissions: function(){
var ctrl = new TranslationSharingController();
var config = ctrl.get('tsconfig');