Merge lp:~jcsackett/juju-gui/remove-charm-extension-in-browser-charm into lp:juju-gui/experimental

Proposed by j.c.sackett
Status: Merged
Merged at revision: 933
Proposed branch: lp:~jcsackett/juju-gui/remove-charm-extension-in-browser-charm
Merge into: lp:juju-gui/experimental
Diff against target: 551 lines (+375/-91)
2 files modified
app/models/charm.js (+112/-5)
test/test_model.js (+263/-86)
To merge this branch: bzr merge lp:~jcsackett/juju-gui/remove-charm-extension-in-browser-charm
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+179005@code.launchpad.net

Description of the change

Removes Charm extension from BrowserCharm

In order to be able to safely remove Charm, BrowserCharm cannot extend it.
-BrowserCharm now extends Model
-BrowserCharm now supports the initialization functions that Charm does (e.g.
setting attr from the provided ID)
-BrowserCharm now has the same `load` pipeline functions (e.g. `sync`)
-Tests have been updated or added to demonstrate new support
-Drive by test cleanup--there were many tests that checked the same exact
things; I've merged these to mitigate the damage from some of the test
repetition I've had to temporarily add.

https://codereview.appspot.com/12608043/

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Reviewers: mp+179005_code.launchpad.net,

Message:
Please take a look.

Description:
Removes Charm extension from BrowserCharm

In order to be able to safely remove Charm, BrowserCharm cannot extend
it.
-BrowserCharm now extends Model
-BrowserCharm now supports the initialization functions that Charm does
(e.g.
setting attr from the provided ID)
-BrowserCharm now has the same `load` pipeline functions (e.g. `sync`)
-Tests have been updated or added to demonstrate new support
-Drive by test cleanup--there were many tests that checked the same
exact
things; I've merged these to mitigate the damage from some of the test
repetition I've had to temporarily add.

https://code.launchpad.net/~jcsackett/juju-gui/remove-charm-extension-in-browser-charm/+merge/179005

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/12608043/

Affected files:
   A [revision details]
   M app/models/charm.js
   M test/test_model.js

Revision history for this message
Richard Harding (rharding) wrote :

LGTM getting closer!

https://codereview.appspot.com/12608043/diff/1/app/models/charm.js
File app/models/charm.js (right):

https://codereview.appspot.com/12608043/diff/1/app/models/charm.js#newcode540
app/models/charm.js:540: if (data.config && data.config.options && !
data.options) {
space after the !

https://codereview.appspot.com/12608043/

Revision history for this message
Madison Scott-Clary (makyo) wrote :
935. By j.c.sackett

Merged trunk.

936. By j.c.sackett

Change from review.

Revision history for this message
j.c.sackett (jcsackett) wrote :

*** Submitted:

Removes Charm extension from BrowserCharm

In order to be able to safely remove Charm, BrowserCharm cannot extend
it.
-BrowserCharm now extends Model
-BrowserCharm now supports the initialization functions that Charm does
(e.g.
setting attr from the provided ID)
-BrowserCharm now has the same `load` pipeline functions (e.g. `sync`)
-Tests have been updated or added to demonstrate new support
-Drive by test cleanup--there were many tests that checked the same
exact
things; I've merged these to mitigate the damage from some of the test
repetition I've had to temporarily add.

R=rharding, matthew.scott
CC=
https://codereview.appspot.com/12608043

https://codereview.appspot.com/12608043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/models/charm.js'
2--- app/models/charm.js 2013-07-31 20:15:56 +0000
3+++ app/models/charm.js 2013-08-07 21:02:27 +0000
4@@ -216,7 +216,7 @@
5 data.is_subordinate = data.subordinate || data.is_subordinate;
6 // Because the old and new charm models have different places for
7 // the options data, this handles the normalization.
8- if (data.config && data.config.options && ! data.options) {
9+ if (data.config && data.config.options && !data.options) {
10 data.options = data.config.options;
11 delete data.config;
12 }
13@@ -397,10 +397,10 @@
14 * Model to represent the Charms from the Charmworld2 Api.
15 *
16 * @class BrowserCharm
17- * @extends {Charm}
18+ * @extends {Y.Model}
19 *
20 */
21- models.BrowserCharm = Y.Base.create('browser-charm', Charm, [], {
22+ models.BrowserCharm = Y.Base.create('browser-charm', Y.Model, [], {
23 // Only care about at most, this number of related charms per interface.
24 maxRelatedCharms: 5,
25
26@@ -486,8 +486,94 @@
27 }
28 if (cfg.id) {
29 this.set('storeId', cfg.id);
30- this.set('id', this.get('scheme') + ':' + cfg.id);
31- }
32+ }
33+ if (cfg.url) {
34+ this.set('id', cfg.url);
35+ }
36+ }
37+ var id = this.get('id'),
38+ parts = parseCharmId(id),
39+ self = this;
40+ if (!parts) {
41+ throw 'Developers must initialize charms with a well-formed id.';
42+ }
43+ this.loaded = false;
44+ this.on('load', function() { this.loaded = true; });
45+ Y.Object.each(parts, function(value, key) {
46+ this.set(key, value);
47+ }, this);
48+ },
49+
50+ sync: function(action, options, callback) {
51+ if (action !== 'read') {
52+ throw (
53+ 'Only use the "read" action; "' + action + '" not supported.');
54+ }
55+ if (Y.Lang.isValue(options.get_charm)) {
56+ // This is an env.
57+ options.get_charm(this.get('id'), function(response) {
58+ if (response.err) {
59+ callback(true, response);
60+ } else if (response.result) {
61+ callback(false, response.result);
62+ } else {
63+ // What's going on? This does not look like either of our
64+ // expected signatures. Declare a loading error.
65+ callback(true, response);
66+ }
67+ });
68+ } else {
69+ throw 'You must supply a get_charm function.';
70+ }
71+ },
72+
73+ parse: function(response) {
74+ var data = Charm.superclass.parse.apply(this, arguments),
75+ self = this;
76+
77+ // TODO (gary): verify whether is_subordinate is ever passed by pyjuju
78+ // or juju core. If not, remove the "|| data.is_subordinate" and change
79+ // in the fakebackend and/or sandbox to send the expected thing there.
80+ data.is_subordinate = data.subordinate || data.is_subordinate;
81+ // Because the old and new charm models have different places for
82+ // the options data, this handles the normalization.
83+ if (data.config && data.config.options && !data.options) {
84+ data.options = data.config.options;
85+ delete data.config;
86+ }
87+ Y.each(data, function(value, key) {
88+ if (!Y.Lang.isValue(value) ||
89+ !self.attrAdded(key) ||
90+ Y.Lang.isValue(self.get(key))) {
91+ delete data[key];
92+ }
93+ });
94+ if (data.owner === 'charmers') {
95+ delete data.owner;
96+ }
97+ return data;
98+ },
99+
100+ compare: function(other, relevance, otherRelevance) {
101+ // Official charms sort before owned charms.
102+ // If !X.owner, that means it is owned by charmers.
103+ var owner = this.get('owner'),
104+ otherOwner = other.get('owner');
105+ if (!owner && otherOwner) {
106+ return -1;
107+ } else if (owner && !otherOwner) {
108+ return 1;
109+ // Relevance is next most important.
110+ } else if (relevance && (relevance !== otherRelevance)) {
111+ // Higher relevance comes first.
112+ return otherRelevance - relevance;
113+ // Otherwise sort by package name, then by owner, then by revision.
114+ } else {
115+ return (
116+ (this.get('package_name').localeCompare(
117+ other.get('package_name'))) ||
118+ (owner ? owner.localeCompare(otherOwner) : 0) ||
119+ (this.get('revision') - other.get('revision')));
120 }
121 },
122
123@@ -562,6 +648,27 @@
124 changelog: {
125 value: {}
126 },
127+ //XXX jcsackett Aug 7 2013 This attribute is only needed until we turn
128+ // on the service inspector. It's just used by the charm view you get when
129+ // inspecting a service, and should be ripped out (along with tests) when
130+ // we remove that view.
131+ charm_path: {
132+ /**
133+ * Generate the charm store path from the attributes of the charm.
134+ *
135+ * @method getter
136+ *
137+ */
138+ getter: function() {
139+ var owner = this.get('owner');
140+ return [
141+ (owner ? '~' + owner : 'charms'),
142+ this.get('series'),
143+ (this.get('package_name') + '-' + this.get('revision')),
144+ 'json'
145+ ].join('/');
146+ }
147+ },
148 /**
149 * Object of data about the source for this charm including bugs link,
150 * log, revisions, etc.
151
152=== modified file 'test/test_model.js'
153--- test/test_model.js 2013-07-31 20:15:56 +0000
154+++ test/test_model.js 2013-08-07 21:02:27 +0000
155@@ -16,10 +16,9 @@
156 with this program. If not, see <http://www.gnu.org/licenses/>.
157 */
158
159-
160 'use strict';
161
162-describe('charm normalization', function() {
163+describe('Charm and BrowserCharm initialization', function() {
164 var models;
165
166 before(function(done) {
167@@ -29,19 +28,101 @@
168 });
169 });
170
171- it('must create derived attributes from official charm id', function() {
172+ it('must be able to create Charm', function() {
173 var charm = new models.Charm(
174- {id: 'cs:precise/openstack-dashboard-0'});
175- charm.get('scheme').should.equal('cs');
176- var _ = expect(charm.get('owner')).to.not.exist;
177- charm.get('full_name').should.equal('precise/openstack-dashboard');
178- charm.get('charm_path').should.equal(
179- 'charms/precise/openstack-dashboard-0/json');
180+ {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});
181+ charm.get('scheme').should.equal('cs');
182+ charm.get('owner').should.equal('alt-bac');
183+ charm.get('series').should.equal('precise');
184+ charm.get('package_name').should.equal('openstack-dashboard');
185+ charm.get('revision').should.equal(0);
186+ charm.get('full_name').should.equal(
187+ '~alt-bac/precise/openstack-dashboard');
188+ charm.get('charm_path').should.equal(
189+ '~alt-bac/precise/openstack-dashboard-0/json');
190+ });
191+
192+ it('must be able to create BrowserCharm', function() {
193+ var charm = new models.BrowserCharm(
194+ {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});
195+ charm.get('scheme').should.equal('cs');
196+ charm.get('owner').should.equal('alt-bac');
197+ charm.get('series').should.equal('precise');
198+ charm.get('package_name').should.equal('openstack-dashboard');
199+ charm.get('revision').should.equal(0);
200+ charm.get('full_name').should.equal(
201+ '~alt-bac/precise/openstack-dashboard');
202+ charm.get('charm_path').should.equal(
203+ '~alt-bac/precise/openstack-dashboard-0/json');
204+ });
205+
206+ it('must not set "owner" for promulgated charms', function() {
207+ var charm;
208+ charm = new models.Charm({id: 'cs:precise/openstack-dashboard-0'});
209+ assert.isUndefined(charm.get('owner'));
210+
211+ charm = new models.BrowserCharm({
212+ id: 'cs:precise/openstack-dashboard-0'
213+ });
214+ assert.isUndefined(charm.get('owner'));
215+ });
216+
217+ it('must be able to parse hyphenated owner names', function() {
218+ // Note that an earlier version of the parsing code did not handle
219+ // hyphens in user names, so this test intentionally includes one.
220+ var charm;
221+ charm = new models.Charm(
222+ {id: 'cs:~marco-ceppi/precise/wordpress-17'});
223+ charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');
224+
225+ charm = new models.BrowserCharm(
226+ {id: 'cs:~marco-ceppi/precise/wordpress-17'});
227+ charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');
228+ });
229+
230+ it('must reject bad charm ids.', function() {
231+ var charm;
232+ try {
233+ charm = new models.Charm({id: 'foobar'});
234+ assert.fail('Should have thrown an error');
235+ } catch (e) {
236+ e.should.equal(
237+ 'Developers must initialize charms with a well-formed id.');
238+ }
239+
240+ try {
241+ charm = new models.BrowserCharm({id: 'foobar'});
242+ assert.fail('Should have thrown an error');
243+ } catch (e) {
244+ e.should.equal(
245+ 'Developers must initialize charms with a well-formed id.');
246+ }
247+ });
248+
249+
250+ it('must reject missing charm ids at initialization.', function() {
251+ var charm;
252+ try {
253+ charm = new models.Charm();
254+ assert.fail('Should have thrown an error');
255+ } catch (e) {
256+ e.should.equal(
257+ 'Developers must initialize charms with a well-formed id.');
258+ }
259+
260+ try {
261+ charm = new models.BrowserCharm();
262+ assert.fail('Should have thrown an error');
263+ } catch (e) {
264+ e.should.equal(
265+ 'Developers must initialize charms with a well-formed id.');
266+ }
267 });
268
269 it('can load options from both "options" and "config"', function() {
270- var options = {foo: 'bar'};
271- var charm = new models.Charm({
272+ var options = {foo: 'bar'},
273+ charm;
274+ charm = new models.Charm({
275 id: 'cs:precise/openstack-dashboard-0',
276 options: options
277 });
278@@ -55,7 +136,7 @@
279 assert.equal(charm.get('options'), options);
280 });
281
282- it('must convert timestamps into time objects', function() {
283+ it('must convert timestamps into time objects on Charm', function() {
284 var time = 1349797266.032,
285 date = new Date(time),
286 charm = new models.Charm(
287@@ -63,6 +144,13 @@
288 charm.get('last_change').created.should.eql(date);
289 });
290
291+ it('must convert timestamps into time objects on BrowserCharm', function() {
292+ var time = 1349797266.032,
293+ date = new Date(time),
294+ charm = new models.BrowserCharm(
295+ { id: 'cs:precise/foo-9', last_change: {created: time / 1000} });
296+ charm.get('last_change').created.should.eql(date);
297+ });
298 });
299
300 describe('juju models', function() {
301@@ -79,79 +167,6 @@
302 window._gaq = [];
303 });
304
305- it('must be able to create charm', function() {
306- var charm = new models.Charm(
307- {id: 'cs:~alt-bac/precise/openstack-dashboard-0'});
308- charm.get('scheme').should.equal('cs');
309- charm.get('owner').should.equal('alt-bac');
310- charm.get('series').should.equal('precise');
311- charm.get('package_name').should.equal('openstack-dashboard');
312- charm.get('revision').should.equal(0);
313- charm.get('full_name').should.equal(
314- '~alt-bac/precise/openstack-dashboard');
315- charm.get('charm_path').should.equal(
316- '~alt-bac/precise/openstack-dashboard-0/json');
317- });
318-
319- it('must be able to parse real-world charm names', function() {
320- var charm = new models.Charm({id: 'cs:precise/openstack-dashboard-0'});
321- charm.get('full_name').should.equal('precise/openstack-dashboard');
322- charm.get('package_name').should.equal('openstack-dashboard');
323- charm.get('charm_path').should.equal(
324- 'charms/precise/openstack-dashboard-0/json');
325- charm.get('scheme').should.equal('cs');
326- var _ = expect(charm.get('owner')).to.not.exist;
327- charm.get('series').should.equal('precise');
328- charm.get('package_name').should.equal('openstack-dashboard');
329- charm.get('revision').should.equal(0);
330- });
331-
332- it('must be able to parse individually owned charms', function() {
333- // Note that an earlier version of the parsing code did not handle
334- // hyphens in user names, so this test intentionally includes one.
335- var charm = new models.Charm(
336- {id: 'cs:~marco-ceppi/precise/wordpress-17'});
337- charm.get('full_name').should.equal('~marco-ceppi/precise/wordpress');
338- charm.get('package_name').should.equal('wordpress');
339- charm.get('charm_path').should.equal(
340- '~marco-ceppi/precise/wordpress-17/json');
341- charm.get('revision').should.equal(17);
342- });
343-
344- it('must reject bad charm ids.', function() {
345- try {
346- var charm = new models.Charm({id: 'foobar'});
347- assert.fail('Should have thrown an error');
348- } catch (e) {
349- e.should.equal(
350- 'Developers must initialize charms with a well-formed id.');
351- }
352- });
353-
354- it('must reject missing charm ids at initialization.', function() {
355- try {
356- var charm = new models.Charm();
357- assert.fail('Should have thrown an error');
358- } catch (e) {
359- e.should.equal(
360- 'Developers must initialize charms with a well-formed id.');
361- }
362- });
363-
364- it('must be able to create charm list', function() {
365- var c1 = new models.Charm(
366- { id: 'cs:precise/mysql-2',
367- description: 'A DB'}),
368- c2 = new models.Charm(
369- { id: 'cs:precise/logger-3',
370- description: 'Log sub'}),
371- clist = new models.CharmList();
372- clist.add([c1, c2]);
373- var names = clist.map(function(c) {return c.get('package_name');});
374- names[0].should.equal('mysql');
375- names[1].should.equal('logger');
376- });
377-
378 it('service unit list should be able to get units of a given service',
379 function() {
380 var sl = new models.ServiceList();
381@@ -491,7 +506,169 @@
382 });
383 });
384
385-describe('juju charm load', function() {
386+// XXX jcsackett August 7 2013 This is a complete duplication of the next test
387+// case, but we need to test independently for both models while we're merging
388+// them. The Charm load suite below can be removed once the work is done.
389+describe('BrowserCharm load', function() {
390+ var Y, models, conn, env, app, container, fakeStore, data, juju;
391+
392+ before(function(done) {
393+ Y = YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',
394+ 'juju-tests-utils', 'json-stringify',
395+ 'juju-charm-store'], function(Y) {
396+ models = Y.namespace('juju.models');
397+ juju = Y.namespace('juju');
398+ done();
399+ });
400+ });
401+
402+ beforeEach(function() {
403+ conn = new (Y.namespace('juju-tests.utils')).SocketStub();
404+ env = juju.newEnvironment({conn: conn});
405+ env.connect();
406+ conn.open();
407+ container = Y.Node.create('<div id="test" class="container"></div>');
408+ data = [];
409+ fakeStore = new Y.juju.Charmworld2({});
410+ fakeStore.set('datasource', {
411+ sendRequest: function(params) {
412+ params.callback.success({
413+ response: {
414+ results: [{
415+ responseText: data
416+ }]
417+ }
418+ });
419+ }
420+ });
421+ });
422+
423+ afterEach(function() {
424+ container.destroy();
425+ });
426+
427+ it('will throw an exception with non-read sync', function() {
428+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'});
429+ try {
430+ charm.sync('create');
431+ assert.fail('Should have thrown an error');
432+ } catch (e) {
433+ e.should.equal('Only use the "read" action; "create" not supported.');
434+ }
435+ try {
436+ charm.sync('update');
437+ assert.fail('Should have thrown an error');
438+ } catch (e) {
439+ e.should.equal('Only use the "read" action; "update" not supported.');
440+ }
441+ try {
442+ charm.sync('delete');
443+ assert.fail('Should have thrown an error');
444+ } catch (e) {
445+ e.should.equal('Only use the "read" action; "delete" not supported.');
446+ }
447+ });
448+
449+ it('throws an error if you do not pass get_charm',
450+ function() {
451+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'});
452+ try {
453+ charm.sync('read', {});
454+ assert.fail('Should have thrown an error');
455+ } catch (e) {
456+ e.should.equal(
457+ 'You must supply a get_charm function.');
458+ }
459+ try {
460+ charm.sync('read', {env: 42});
461+ assert.fail('Should have thrown an error');
462+ } catch (e) {
463+ e.should.equal(
464+ 'You must supply a get_charm function.');
465+ }
466+ });
467+
468+ it('must send request to juju environment for local charms', function() {
469+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(env);
470+ assert(!charm.loaded);
471+ conn.last_message().op.should.equal('get_charm');
472+ });
473+
474+ it('must handle success from local charm request', function(done) {
475+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(
476+ env,
477+ function(err, response) {
478+ assert(!err);
479+ charm.get('summary').should.equal('wowza');
480+ assert(charm.loaded);
481+ done();
482+ });
483+ var response = conn.last_message();
484+ response.result = { summary: 'wowza' };
485+ env.dispatch_result(response);
486+ // The test in the callback above should run.
487+ });
488+
489+ it('parses the old charm model options location correctly', function(done) {
490+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(
491+ env,
492+ function(err, response) {
493+ assert(!err);
494+ // This checks to make sure the parse mechanism is working properly
495+ // for both the old ane new charm browser.
496+ assert.equal(charm.get('options').default_log['default'], 'global');
497+ done();
498+ });
499+ var response = conn.last_message();
500+ response.result = {
501+ config: {
502+ options: {
503+ default_log: {
504+ 'default': 'global',
505+ description: 'Default log',
506+ type: 'string'
507+ }}}};
508+ env.dispatch_result(response);
509+ });
510+
511+ it('parses the new charm model options location correctly', function(done) {
512+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(
513+ env,
514+ function(err, response) {
515+ assert(!err);
516+ // This checks to make sure the parse mechanism is working properly
517+ // for both the old ane new charm browser.
518+ assert.equal(charm.get('options').default_log['default'], 'global');
519+ done();
520+ });
521+ var response = conn.last_message();
522+ response.result = {
523+ options: {
524+ default_log: {
525+ 'default': 'global',
526+ description: 'Default log',
527+ type: 'string'
528+ }}};
529+ env.dispatch_result(response);
530+ });
531+
532+ it('must handle failure from local charm request', function(done) {
533+ var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(
534+ env,
535+ function(err, response) {
536+ assert(err);
537+ assert(response.err);
538+ assert(!charm.loaded);
539+ done();
540+ });
541+ var response = conn.last_message();
542+ response.err = true;
543+ env.dispatch_result(response);
544+ // The test in the callback above should run.
545+ });
546+});
547+
548+describe('Charm load', function() {
549 var Y, models, conn, env, app, container, fakeStore, data, juju;
550
551 before(function(done) {

Subscribers

People subscribed via source and target branches