Merge lp:~huwshimi/juju-gui/bundle-charm-list-updates into lp:juju-gui/experimental
- bundle-charm-list-updates
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1151 |
Proposed branch: | lp:~huwshimi/juju-gui/bundle-charm-list-updates |
Merge into: | lp:juju-gui/experimental |
Diff against target: |
726 lines (+265/-247) 4 files modified
app/subapps/browser/views/bundle.js (+76/-54) app/templates/bundle.handlebars (+22/-10) lib/views/browser/bundle-panel.less (+3/-0) test/test_bundle_details_view.js (+164/-183) |
To merge this branch: | bzr merge lp:~huwshimi/juju-gui/bundle-charm-list-updates |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email:
|
Commit message
Description of the change
Bundle promise removal and service updates
Removed promises (merged from Rick)
Visual updates:
1) summary tab: delete charm stuff
2) change charms tab name to services
3) add service name to top of config info
4) sort services by charm name and then by service name
5) Add this text to top of Services tab: "These are the services that the bundle will deploy. Non-default configuration values are listed."
6) if charm is subordinate, do not show number of units; if charm is not subordinate and does not list number of units, show "1"
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Huw Wilkins (huwshimi) wrote : | # |
- 1150. By Huw Wilkins
-
Merged trunk.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Huw Wilkins (huwshimi) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gary Poster (gary) wrote : | # |
I'd like to talk through my comments before giving a blessing, but it
looks very close. Thank you!
QA good. I think I want to try and get rid of the Summary tab, but that
can be a separate branch, maybe based on some work Benji and Brad are
doing.
https:/
File app/subapps/
https:/
app/subapps/
_model is a pretty confusing name, IMO. Why not simply service._charm?
Also, this is using an underline effectively as a namespace tool, which
I don't love.
https:/
app/subapps/
The 'entity' is a bundle here, right? It is called 'entity' because we
are sharing code with a charm view as well, right, so in other files
'entity' is a charm? If so, please add a comment explaining this (at
least the fact that this.get('entity') is a bundle).
https:/
app/subapps/
I'd personally feel that this would be an easier-to-read data structure
if you changed this approach. For instance, one example would be to
build a new services mapping that was like this:
newServicesMapp
data: service}.
https:/
app/subapps/
Per Jeff's comments
(http://
this should be in a separate .then(null, function(error) {...}) block so
that you can also catch and report errors from the success function.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Richard Harding (rharding) wrote : | # |
O>
https:/
> app/subapps/
> _model is a pretty confusing name, IMO. Why not simply
service._charm? Also,
> this is using an underline effectively as a namespace tool, which I
don't love.
We have to be careful not to collide with keys that could occur in the
service block in the deployer file section. Using the _ is a
"collision-
https:/
> app/subapps/
> The 'entity' is a bundle here, right? It is called 'entity' because
we are
> sharing code with a charm view as well, right, so in other files
'entity' is a
> charm? If so, please add a comment explaining this (at least the fact
that
> this.get('entity') is a bundle).
Yep, the details shares a lot of common code in entity-base.js which was
refactored out to share between charm and bundle details views.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gary Poster (gary) wrote : | # |
On 2013/10/23 13:57:52, rharding wrote:
> O>
> >
https:/
> > app/subapps/
> > _model is a pretty confusing name, IMO. Why not simply
service._charm? Also,
> > this is using an underline effectively as a namespace tool, which I
don't
> love.
> We have to be careful not to collide with keys that could occur in the
service
> block in the deployer file section. Using the _ is a
"collision-
Right, that's what I meant by namespace
> What
> would you prefer as an alternative?
See my comment starting with "I'd personally feel that this would be an
easier-to-read data structure..."
> >
https:/
> > app/subapps/
> > The 'entity' is a bundle here, right? It is called 'entity' because
we are
> > sharing code with a charm view as well, right, so in other files
'entity' is a
> > charm? If so, please add a comment explaining this (at least the
fact that
> > this.get('entity') is a bundle).
> Yep, the details shares a lot of common code in entity-base.js which
was
> refactored out to share between charm and bundle details views.
Cool.
Thank you.
- 1151. By Huw Wilkins
-
Review changes.
- 1152. By Huw Wilkins
-
Merged trunk.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Huw Wilkins (huwshimi) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jeff Pihach (hatch) wrote : | # |
LGTM after some small changes below - Thanks for putting in the time on
this one!
https:/
File app/subapps/
https:/
app/subapps/
this.get(
You have all of this information when you call this function so as a
micro performance improvement you could pass only the required data in
so that this is a utility method instead of just a private method which
isn't really a private method.
https:/
app/subapps/
Y.merge(
If bundle is a Y.Model then this should be returning a new object so
this merge is not required. Then you can use bundleData below instead of
templateData
https:/
app/subapps/
topology is rendered
Fire event to listen to during the tests so that we know when it's
rendered
https:/
app/subapps/
Now that render is synchronous this is no longer required and the checks
can be removed from the tests.
- 1153. By Huw Wilkins
-
Review fixes.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Huw Wilkins (huwshimi) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Huw Wilkins (huwshimi) wrote : | # |
*** Submitted:
Bundle promise removal and service updates
Removed promises (merged from Rick)
Visual updates:
1) summary tab: delete charm stuff
2) change charms tab name to services
3) add service name to top of config info
4) sort services by charm name and then by service name
5) Add this text to top of Services tab: "These are the services that
the bundle will deploy. Non-default configuration values are listed."
6) if charm is subordinate, do not show number of units; if charm is not
subordinate and does not list number of units, show "1"
R=gary.poster, rharding, jeff.pihach
CC=
https:/
Preview Diff
1 | === modified file 'app/subapps/browser/views/bundle.js' |
2 | --- app/subapps/browser/views/bundle.js 2013-10-17 16:55:19 +0000 |
3 | +++ app/subapps/browser/views/bundle.js 2013-10-24 00:19:58 +0000 |
4 | @@ -77,34 +77,6 @@ |
5 | }, |
6 | |
7 | /** |
8 | - Fetches and prepares the data for the bundle details page rendering. |
9 | - |
10 | - @method _fetchData |
11 | - */ |
12 | - _fetchData: function() { |
13 | - var self = this; |
14 | - |
15 | - return new Y.Promise(function(resolve, reject) { |
16 | - var entity = self.get('entity'); |
17 | - // An entity here is a fully populated charm/bundle model so |
18 | - // it's entirely possible that we have an id to load but |
19 | - // no model has been populated yet. |
20 | - if (entity) { |
21 | - resolve(entity); |
22 | - } else { |
23 | - self.get('store').bundle(self.get('entityId'), { |
24 | - 'success': function(data) { |
25 | - var bundle = new models.Bundle(data); |
26 | - self.set('entity', bundle); |
27 | - resolve(bundle); |
28 | - }, |
29 | - 'failure': reject |
30 | - }, self); |
31 | - } |
32 | - }); |
33 | - }, |
34 | - |
35 | - /** |
36 | Sends the bundle data to the local fakebackend to |
37 | import and then returns a promise when complete. |
38 | |
39 | @@ -140,36 +112,66 @@ |
40 | Render the list of charms in the bundle. |
41 | |
42 | @method _renderCharmListing |
43 | + @param {Object} services the services in the bundle. |
44 | |
45 | */ |
46 | - _renderCharmListing: function() { |
47 | - var attrs = this.get('entity').getAttrs(); |
48 | - Y.Object.each(attrs.charm_metadata, function(charm, key) { |
49 | - var charmModel = new Y.juju.models.Charm(charm); |
50 | - charm = charmModel.getAttrs(); |
51 | + _renderCharmListing: function(services) { |
52 | + services.forEach(function(service) { |
53 | + var charm = service.charmModel.getAttrs(); |
54 | charm.size = 'tiny'; |
55 | charm.isDraggable = false; |
56 | var token = new widgets.browser.Token(charm); |
57 | - var node = Y.one('[data-config="' + key + '"]'); |
58 | + var node = Y.one('[data-config="' + service.origService.name + '"]'); |
59 | token.render(node); |
60 | this._cleanup.tokens.push(token); |
61 | }, this); |
62 | }, |
63 | |
64 | /** |
65 | + Build and order a list of charms. |
66 | + |
67 | + @method _buildCharmList |
68 | + @param {Object} the bundle entity attrs. |
69 | + @return {Array} the ordered list of charms in the bundle. |
70 | + |
71 | + */ |
72 | + _buildCharmList: function(bundleData) { |
73 | + var services = []; |
74 | + Y.Object.each(bundleData.services, function(service, key) { |
75 | + var charm = bundleData.charm_metadata[key]; |
76 | + services.push({ |
77 | + origService: { |
78 | + name: key, |
79 | + data: service |
80 | + }, |
81 | + charmModel: new Y.juju.models.Charm(charm) |
82 | + }); |
83 | + }, this); |
84 | + services.sort(function(a, b) { |
85 | + return a.charmModel.get('name') > b.charmModel.get('name') ? 1 : -1; |
86 | + }); |
87 | + return services; |
88 | + }, |
89 | + |
90 | + /** |
91 | Renders the bundle view template into the DOM. |
92 | |
93 | @method _renderBundleView |
94 | */ |
95 | _renderBundleView: function() { |
96 | - var entity = this.get('entity'); |
97 | - var attrs = entity.getAttrs(); |
98 | - attrs.charmIcons = utils.charmIconParser(attrs.charm_metadata); |
99 | + var bundle = this.get('entity'); |
100 | + var bundleData = bundle.getAttrs(); |
101 | + // Copy the bundle for use in the template so we can modify the content |
102 | + // without munipulating the entity. |
103 | + var templateData = Y.merge(bundleData); |
104 | + templateData.charmIcons = utils.charmIconParser( |
105 | + templateData.charm_metadata); |
106 | // Remove the svg files from the file list |
107 | - attrs.files = attrs.files.filter(function(fileName) { |
108 | + templateData.files = templateData.files.filter(function(fileName) { |
109 | return !/\.svg$/.test(fileName); |
110 | }); |
111 | - var content = this.template(attrs); |
112 | + templateData.services = this._buildCharmList(bundleData); |
113 | + var content = this.template(templateData); |
114 | var node = this.get('container').setHTML(content); |
115 | var renderTo = this.get('renderTo'); |
116 | var options = {size: [480, 360]}; |
117 | @@ -179,15 +181,27 @@ |
118 | // remove the flag in the test(test_bundle_details_view.js) |
119 | // when this flag is no longer needed. |
120 | if (window.flags && window.flags.strictBundle) { |
121 | - showTopo = this._positionAnnotationsIncluded(attrs.data.services); |
122 | + showTopo = this._positionAnnotationsIncluded( |
123 | + bundleData.data.services); |
124 | } |
125 | if (showTopo) { |
126 | - this.environment = new views.BundleTopology(Y.mix({ |
127 | - db: this.fakebackend.db, |
128 | - container: node.one('#bws-bundle'), // Id because of Y.TabView |
129 | - store: this.get('store') |
130 | - }, options)); |
131 | - this.environment.render(); |
132 | + // Setup the fake backend to create topology to display the canvas-like |
133 | + // rendering of the bundle. |
134 | + this._setupLocalFakebackend(); |
135 | + var self = this; |
136 | + this._parseData(bundle).then(function() { |
137 | + self.environment = new views.BundleTopology(Y.mix({ |
138 | + db: self.fakebackend.db, |
139 | + container: node.one('#bws-bundle'), // Id because of Y.TabView |
140 | + store: self.get('store') |
141 | + }, options)); |
142 | + self.environment.render(); |
143 | + // Fire event to listen to during the tests so that we know when |
144 | + // it's rendered. |
145 | + self.fire('topologyRendered'); |
146 | + }).then(null, function(error) { |
147 | + console.error(error.message, error); |
148 | + }); |
149 | } else { |
150 | // Remove the bundle tab so it doesn't get PE'd when |
151 | // we instantiate the tabview. |
152 | @@ -205,9 +219,7 @@ |
153 | } |
154 | this._dispatchTabEvents(this.tabview); |
155 | this._showActiveTab(); |
156 | - this._renderCharmListing(); |
157 | - |
158 | - this.set('rendered', true); |
159 | + this._renderCharmListing(templateData.services); |
160 | }, |
161 | |
162 | /** |
163 | @@ -266,11 +278,20 @@ |
164 | */ |
165 | render: function() { |
166 | this.showIndicator(this.get('renderTo')); |
167 | - this._setupLocalFakebackend(); |
168 | - this._fetchData(). |
169 | - then(this._parseData.bind(this)). |
170 | - then(this._renderBundleView.bind(this)). |
171 | - then(null, this.apiFailure.bind(this)); |
172 | + var entity = this.get('entity'); |
173 | + if (entity) { |
174 | + this._renderBundleView(); |
175 | + } else { |
176 | + this.get('store').bundle( |
177 | + this.get('entityId'), { |
178 | + 'success': function(data) { |
179 | + this.set('entity', new models.Bundle(data)); |
180 | + this._renderBundleView(); |
181 | + }, |
182 | + 'failure': this.apiFailure |
183 | + }, |
184 | + this); |
185 | + } |
186 | } |
187 | |
188 | }, { |
189 | @@ -291,6 +312,7 @@ |
190 | }, '', { |
191 | requires: [ |
192 | 'browser-overlay-indicator', |
193 | + 'juju-bundle-models', |
194 | 'juju-charm-models', |
195 | 'juju-view-utils', |
196 | 'view', |
197 | |
198 | === modified file 'app/templates/bundle.handlebars' |
199 | --- app/templates/bundle.handlebars 2013-10-17 06:12:35 +0000 |
200 | +++ app/templates/bundle.handlebars 2013-10-24 00:19:58 +0000 |
201 | @@ -39,7 +39,7 @@ |
202 | <li><a href="#bws-bundle" class="bundle">Bundle</a></li> |
203 | <li><a href="#bws-summary" class="summary">Summary</a></li> |
204 | <li><a href="#bws-readme" class="readme">Readme</a></li> |
205 | - <li><a href="#bws-charms" class="charms">Charms</a></li> |
206 | + <li><a href="#bws-services" class="services">Services</a></li> |
207 | <li><a href="#bws-code" class="code">Code</a></li> |
208 | </ul> |
209 | <div> |
210 | @@ -54,8 +54,6 @@ |
211 | <p>No description</p> |
212 | {{/if}} |
213 | </div> |
214 | - <h2>Charms within this bundle</h2> |
215 | - [Charm list to go here] |
216 | <div class="changelog"> |
217 | <h3 class="section-title"> |
218 | {{#if recent_commits}} |
219 | @@ -119,7 +117,11 @@ |
220 | </div> |
221 | </div> |
222 | <div id="bws-readme"></div> |
223 | - <div id="bws-charms"> |
224 | + <div id="bws-services"> |
225 | + <p> |
226 | + These are the services that the bundle will deploy. |
227 | + Non-default configuration values are listed. |
228 | + </p> |
229 | <div class="yui3-g"> |
230 | <div class="interfaces-heading yui3-u-1-2"> |
231 | <h3>Charm</h3> |
232 | @@ -129,21 +131,31 @@ |
233 | </div> |
234 | </div> |
235 | <ul class="interface-list"> |
236 | - {{#arrayObject services}} |
237 | + {{#each services}} |
238 | <li class="interface-row yui3-g"> |
239 | - <div class="yui3-u-1-2" data-config="{{ key }}"></div> |
240 | + <div class="yui3-u-1-2" data-config="{{ origService.name }}"></div> |
241 | <div class="yui3-u-1-2 charm-config"> |
242 | <ul> |
243 | - <li>Number of units: {{ value.num_units }}</li> |
244 | - {{#if value.config}} |
245 | - {{#arrayObject value.options}} |
246 | + <li>Service name: {{ origService.name }}</li> |
247 | + {{#unless charmModel.is_subordinate}} |
248 | + <li> |
249 | + Number of units: |
250 | + {{#if num_units }} |
251 | + {{ num_units }} |
252 | + {{else}} |
253 | + 1 |
254 | + {{/if}} |
255 | + </li> |
256 | + {{/unless}} |
257 | + {{#if origService.options}} |
258 | + {{#arrayObject origService.options}} |
259 | <li>{{ key }}: {{ value }}</li> |
260 | {{/arrayObject}} |
261 | {{/if}} |
262 | </ul> |
263 | </div> |
264 | </li> |
265 | - {{/arrayObject}} |
266 | + {{/each}} |
267 | </ul> |
268 | </div> |
269 | <div id="bws-code"> |
270 | |
271 | === modified file 'lib/views/browser/bundle-panel.less' |
272 | --- lib/views/browser/bundle-panel.less 2013-10-10 00:05:02 +0000 |
273 | +++ lib/views/browser/bundle-panel.less 2013-10-24 00:19:58 +0000 |
274 | @@ -50,4 +50,7 @@ |
275 | .topology-canvas { |
276 | .crosshatch-background; |
277 | } |
278 | + #bws-services { |
279 | + padding-top: 50px; |
280 | + } |
281 | } |
282 | |
283 | === modified file 'test/test_bundle_details_view.js' |
284 | --- test/test_bundle_details_view.js 2013-10-17 16:55:19 +0000 |
285 | +++ test/test_bundle_details_view.js 2013-10-24 00:19:58 +0000 |
286 | @@ -19,11 +19,12 @@ |
287 | 'use strict'; |
288 | |
289 | describe('Browser bundle detail view', function() { |
290 | - var Y, utils, data, container, origData, view, fakestore, browser; |
291 | + var Y, cleanUp, utils, data, container, view, fakestore, browser, models; |
292 | |
293 | before(function(done) { |
294 | Y = YUI(GlobalConfig).use( |
295 | 'view', |
296 | + 'juju-bundle-models', |
297 | 'juju-env-fakebackend', |
298 | 'juju-view-bundle', |
299 | 'subapp-browser', // required for handlebars helpers |
300 | @@ -36,14 +37,13 @@ |
301 | 'event-simulate', |
302 | 'node-event-simulate', |
303 | function(Y) { |
304 | + models = Y.namespace('juju.models'); |
305 | utils = Y.namespace('juju-tests.utils'); |
306 | - var sampleData = Y.io('data/browserbundle.json', {sync: true}); |
307 | - |
308 | - origData = Y.JSON.parse(sampleData.responseText); |
309 | + data = utils.loadFixture('data/browserbundle.json', true); |
310 | |
311 | // Required to register the handlebars helpers |
312 | browser = new Y.juju.subapps.Browser({ |
313 | - store: modifyFakeStore() |
314 | + store: utils.makeFakeStore() |
315 | }); |
316 | |
317 | done(); |
318 | @@ -55,19 +55,20 @@ |
319 | view._setupLocalFakebackend = function() { |
320 | this.fakebackend = utils.makeFakeBackend(); |
321 | }; |
322 | + cleanUp = utils.stubCharmIconPath(); |
323 | }); |
324 | |
325 | afterEach(function() { |
326 | container.remove().destroy(true); |
327 | view.destroy(); |
328 | + cleanUp(); |
329 | }); |
330 | |
331 | function generateBundleView(options) { |
332 | - data = Y.clone(origData); |
333 | container = utils.makeContainer(); |
334 | container.append('<div class="bws-view-data"></div>'); |
335 | var defaults = { |
336 | - store: modifyFakeStore(), |
337 | + store: utils.makeFakeStore(3), |
338 | db: {}, |
339 | entityId: data.id, |
340 | renderTo: container |
341 | @@ -77,85 +78,60 @@ |
342 | return view; |
343 | } |
344 | |
345 | - function modifyFakeStore(options) { |
346 | - var defaults = { |
347 | - bundle: function(id, callbacks) { |
348 | - callbacks.success(data); |
349 | - }, |
350 | - iconpath: function(id) { |
351 | - return 'foo'; |
352 | - } |
353 | - }; |
354 | - |
355 | - var fakebackend = Y.mix(defaults, options, true); |
356 | - if (view) { |
357 | - view.set('store', fakebackend); |
358 | - } |
359 | - return fakebackend; |
360 | - } |
361 | - |
362 | it('can be instantiated', function() { |
363 | assert.equal(view instanceof Y.juju.browser.views.BrowserBundleView, true); |
364 | }); |
365 | |
366 | - it('displays the bundle data in a tabview', function(done) { |
367 | - view.after('renderedChange', function(e) { |
368 | - assert.isNotNull(container.one('.yui3-tabview')); |
369 | - done(); |
370 | - }); |
371 | + it('displays the bundle data in a tabview', function() { |
372 | + view.set('entity', new models.Bundle(data)); |
373 | view.render(); |
374 | + assert.isNotNull(container.one('.yui3-tabview')); |
375 | }); |
376 | |
377 | it('fetches the readme when requested', function(done) { |
378 | - modifyFakeStore({ |
379 | - file: function(id, filename, entityType, callbacks) { |
380 | - assert.equal(entityType, 'bundle'); |
381 | - assert.equal(id, data.id); |
382 | - assert.equal(filename, 'README'); |
383 | - assert.isFunction(callbacks.success); |
384 | - assert.isFunction(callbacks.failure); |
385 | - callbacks.success.call(view, '<div id="testit"></div>'); |
386 | - assert.isNotNull(container.one('#testit')); |
387 | - done(); |
388 | - } |
389 | - }); |
390 | - view.after('renderedChange', function(e) { |
391 | - container.one('a.readme').simulate('click'); |
392 | - }); |
393 | + var fakeStore = utils.makeFakeStore(); |
394 | + fakeStore.file = function(id, filename, entityType, callbacks) { |
395 | + assert.equal(entityType, 'bundle'); |
396 | + assert.equal(id, data.id); |
397 | + assert.equal(filename, 'README'); |
398 | + assert.isFunction(callbacks.success); |
399 | + assert.isFunction(callbacks.failure); |
400 | + callbacks.success.call(view, '<div id="testit"></div>'); |
401 | + assert.isNotNull(container.one('#testit')); |
402 | + done(); |
403 | + }; |
404 | + view.set('store', fakeStore); |
405 | + view.set('entity', new models.Bundle(data)); |
406 | view.render(); |
407 | + container.one('a.readme').simulate('click'); |
408 | }); |
409 | |
410 | it('fetches a source file when requested', function(done) { |
411 | - modifyFakeStore({ |
412 | - file: function(id, filename, entityType, callbacks) { |
413 | - assert.equal(entityType, 'bundle'); |
414 | - assert.equal(id, data.id); |
415 | - assert.equal(filename, 'bundles.yaml'); |
416 | - assert.isFunction(callbacks.success); |
417 | - assert.isFunction(callbacks.failure); |
418 | - callbacks.success.call(view, '<div id="testit"></div>'); |
419 | - assert.isNotNull(container.one('#testit')); |
420 | - done(); |
421 | - } |
422 | - }); |
423 | - view.after('renderedChange', function(e) { |
424 | - container.one('a.code').simulate('click'); |
425 | - var codeNode = container.one('#bws-code'); |
426 | - codeNode.all('select option').item(2).set('selected', 'selected'); |
427 | - codeNode.one('select').simulate('change'); |
428 | - }); |
429 | - |
430 | - view.render(); |
431 | - }); |
432 | - |
433 | - it('renders the proper charm icons into the header', function(done) { |
434 | - view.after('renderedChange', function(e) { |
435 | - assert.equal( |
436 | - container.one('.header .details .charms').all('img').size(), |
437 | - 4); |
438 | + var fakeStore = utils.makeFakeStore(); |
439 | + fakeStore.file = function(id, filename, entityType, callbacks) { |
440 | + assert.equal(entityType, 'bundle'); |
441 | + assert.equal(id, data.id); |
442 | + assert.equal(filename, 'bundles.yaml'); |
443 | + assert.isFunction(callbacks.success); |
444 | + assert.isFunction(callbacks.failure); |
445 | + callbacks.success.call(view, '<div id="testit"></div>'); |
446 | + assert.isNotNull(container.one('#testit')); |
447 | done(); |
448 | - }); |
449 | - view.render(); |
450 | + }; |
451 | + view.set('store', fakeStore); |
452 | + view.set('entity', new models.Bundle(data)); |
453 | + view.render(); |
454 | + container.one('a.code').simulate('click'); |
455 | + var codeNode = container.one('#bws-code'); |
456 | + codeNode.all('select option').item(2).set('selected', 'selected'); |
457 | + codeNode.one('select').simulate('change'); |
458 | + }); |
459 | + |
460 | + it('renders the proper charm icons into the header', function() { |
461 | + view.set('entity', new models.Bundle(data)); |
462 | + view.render(); |
463 | + assert.equal( |
464 | + container.one('.header .details .charms').all('img').size(), 4); |
465 | }); |
466 | |
467 | it('deploys a bundle when \'add\' button is clicked', function(done) { |
468 | @@ -165,14 +141,13 @@ |
469 | assert.isObject(data); |
470 | done(); |
471 | }); |
472 | - view.after('renderedChange', function(e) { |
473 | - container.one('.bundle .add').simulate('click'); |
474 | - }); |
475 | + view.set('entity', new models.Bundle(data)); |
476 | view.render(); |
477 | + container.one('.bundle .add').simulate('click'); |
478 | }); |
479 | |
480 | it('fails gracefully if services don\'t provide xy annotations', |
481 | - function(done) { |
482 | + function() { |
483 | window.flags = { strictBundle: true }; |
484 | view._parseData = function() { |
485 | return new Y.Promise(function(resolve) { resolve(); }); |
486 | @@ -195,15 +170,13 @@ |
487 | } |
488 | }; |
489 | }}); |
490 | - view.after('renderedChange', function(e) { |
491 | - assert.isNull(container.one('#bws-bundle')); |
492 | - assert.isNull(container.one('a[href=#bws-bundle]')); |
493 | - // Check that the charms tab is the landing tab |
494 | - assert.equal(view.tabview.get('selection').get('index'), 2); |
495 | - window.flags = {}; |
496 | - done(); |
497 | - }); |
498 | + view.set('entity', new models.Bundle(data)); |
499 | view.render(); |
500 | + assert.isNull(container.one('#bws-bundle')); |
501 | + assert.isNull(container.one('a[href=#bws-bundle]')); |
502 | + // Check that the charms tab is the landing tab |
503 | + assert.equal(view.tabview.get('selection').get('index'), 2); |
504 | + window.flags = {}; |
505 | }); |
506 | |
507 | it('renders the bundle topology into the view', function(done) { |
508 | @@ -211,112 +184,120 @@ |
509 | view._parseData = function() { |
510 | return new Y.Promise(function(resolve) { resolve(); }); |
511 | }; |
512 | - view.set('entity', { |
513 | - getAttrs: function() { |
514 | - return { |
515 | - charm_metadata: {}, |
516 | - files: [], |
517 | - data: { |
518 | - services: { |
519 | - foo: { |
520 | - annotations: { |
521 | - 'gui-x': '1', |
522 | - 'gui-y': '2' |
523 | - } |
524 | - }, |
525 | - bar: { |
526 | - annotations: { |
527 | - 'gui-x': '3', |
528 | - 'gui-y': '4' |
529 | - } |
530 | - } |
531 | + var entity = { |
532 | + charm_metadata: { |
533 | + foo: { |
534 | + id: 'precise/foo-9', |
535 | + storeId: 'testid', |
536 | + name: 'foo' |
537 | + }, |
538 | + bar: { |
539 | + id: 'precise/bar-10', |
540 | + storeId: 'testid', |
541 | + name: 'bar' |
542 | + } |
543 | + }, |
544 | + files: [], |
545 | + data: { |
546 | + services: { |
547 | + foo: { |
548 | + annotations: { |
549 | + 'gui-x': '1', |
550 | + 'gui-y': '2' |
551 | + } |
552 | + }, |
553 | + bar: { |
554 | + annotations: { |
555 | + 'gui-x': '3', |
556 | + 'gui-y': '4' |
557 | } |
558 | } |
559 | - }; |
560 | - }}); |
561 | - view.after('renderedChange', function(e) { |
562 | + } |
563 | + } |
564 | + }; |
565 | + view.on('topologyRendered', function(e) { |
566 | assert.isNotNull(container.one('.topology-canvas')); |
567 | // Check that the bundle topology tab is the landing tab. |
568 | assert.equal(view.tabview.get('selection').get('index'), 0); |
569 | window.flags = {}; |
570 | done(); |
571 | }); |
572 | - view.render(); |
573 | - }); |
574 | - |
575 | - it('renders the charm list tab properly', function(done) { |
576 | - view._parseData = function() { |
577 | - return new Y.Promise(function(resolve) { resolve(); }); |
578 | - }; |
579 | - view.set('entity', { |
580 | - getAttrs: function() { |
581 | - return { |
582 | - charm_metadata: { |
583 | - foo: { |
584 | - id: 'precise/foo-9', |
585 | - storeId: 'testid', |
586 | - name: 'foo' |
587 | - }, |
588 | - bar: { |
589 | - id: 'precise/bar-10', |
590 | - storeId: 'testid', |
591 | - name: 'bar' |
592 | - } |
593 | - }, |
594 | - files: [], |
595 | - data: { |
596 | - services: { |
597 | - foo: { |
598 | - annotations: { |
599 | - 'gui-x': '1', |
600 | - 'gui-y': '2' |
601 | - } |
602 | - }, |
603 | - bar: { |
604 | - annotations: { |
605 | - 'gui-x': '3', |
606 | - 'gui-y': '4' |
607 | - } |
608 | - } |
609 | - } |
610 | - }, |
611 | - services: { |
612 | - foo: { |
613 | - annotations: { |
614 | - 'gui-x': '1', |
615 | - 'gui-y': '2' |
616 | - } |
617 | - }, |
618 | - bar: { |
619 | - annotations: { |
620 | - 'gui-x': '3', |
621 | - 'gui-y': '4' |
622 | - } |
623 | - } |
624 | - } |
625 | - }; |
626 | - }}); |
627 | - view.after('renderedChange', function(e) { |
628 | - var tab = container.one('#bws-charms'); |
629 | - assert.equal(tab.all('.token').size(), 2); |
630 | - done(); |
631 | - }); |
632 | - view.render(); |
633 | - }); |
634 | - |
635 | - it('selects the proper tab when given one', function(done) { |
636 | - view = generateBundleView({ |
637 | - activeTab: '#bws-charms' |
638 | - }); |
639 | - view._parseData = function() { |
640 | - return new Y.Promise(function(resolve) { resolve(); }); |
641 | - }; |
642 | - view.render(); |
643 | - view.after('renderedChange', function(e) { |
644 | - var selected = view.get('container').one('.yui3-tab-selected a'); |
645 | - assert.equal(selected.getAttribute('href'), '#bws-charms'); |
646 | - done(); |
647 | - }); |
648 | + view.set('entity', new models.Bundle(entity)); |
649 | + view.render(); |
650 | + }); |
651 | + |
652 | + it('renders the charm list tab properly', function() { |
653 | + // This is not under test. It's async and only causes trouble in other |
654 | + // tests. |
655 | + view._parseData = function() { |
656 | + return new Y.Promise(function(resolve) { resolve(); }); |
657 | + }; |
658 | + |
659 | + var entity = { |
660 | + charm_metadata: { |
661 | + foo: { |
662 | + id: 'precise/foo-9', |
663 | + storeId: 'testid', |
664 | + name: 'foo' |
665 | + }, |
666 | + bar: { |
667 | + id: 'precise/bar-10', |
668 | + storeId: 'testid', |
669 | + name: 'bar' |
670 | + } |
671 | + }, |
672 | + files: [], |
673 | + data: { |
674 | + services: { |
675 | + foo: { |
676 | + annotations: { |
677 | + 'gui-x': '1', |
678 | + 'gui-y': '2' |
679 | + } |
680 | + }, |
681 | + bar: { |
682 | + annotations: { |
683 | + 'gui-x': '3', |
684 | + 'gui-y': '4' |
685 | + } |
686 | + } |
687 | + } |
688 | + }, |
689 | + services: { |
690 | + foo: { |
691 | + annotations: { |
692 | + 'gui-x': '1', |
693 | + 'gui-y': '2' |
694 | + } |
695 | + }, |
696 | + bar: { |
697 | + annotations: { |
698 | + 'gui-x': '3', |
699 | + 'gui-y': '4' |
700 | + } |
701 | + } |
702 | + } |
703 | + }; |
704 | + view.set('entity', new models.Bundle(entity)); |
705 | + view.render(); |
706 | + var tab = container.one('#bws-services'); |
707 | + assert.equal(tab.all('.token').size(), 2); |
708 | + var charmConfigNodes = tab.all('.charm-config'); |
709 | + assert.equal( |
710 | + charmConfigNodes.item(0).one('li').get('text'), 'Service name: bar'); |
711 | + assert.equal( |
712 | + charmConfigNodes.item(1).one('li').get('text'), 'Service name: foo'); |
713 | + }); |
714 | + |
715 | + it('selects the proper tab when given one', function() { |
716 | + view.set('activeTab', '#bws-services'); |
717 | + view._parseData = function() { |
718 | + return new Y.Promise(function(resolve) { resolve(); }); |
719 | + }; |
720 | + view.set('entity', new models.Bundle(data)); |
721 | + view.render(); |
722 | + var selected = view.get('container').one('.yui3-tab-selected a'); |
723 | + assert.equal(selected.getAttribute('href'), '#bws-services'); |
724 | }); |
725 | |
726 | }); |
Reviewers: mp+192264_ code.launchpad. net,
Message:
Please take a look.
Description:
Bundle promise removal and service updates
Removed promises (merged from Rick)
Visual updates:
1) summary tab: delete charm stuff
2) change charms tab name to services
3) add service name to top of config info
4) sort services by charm name and then by service name
5) Add this text to top of Services tab: "These are the services that
the bundle will deploy. Non-default configuration values are listed."
6) if charm is subordinate, do not show number of units; if charm is not
subordinate and does not list number of units, show "1"
https:/ /code.launchpad .net/~huwshimi/ juju-gui/ bundle- charm-list- updates/ +merge/ 192264
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/14920057/
Affected files (+236, -213 lines): subapps/ browser/ views/bundle. js templates/ bundle. handlebars views/browser/ bundle- panel.less test_bundle_ details_ view.js
[revision details]
app/
app/
lib/
test/