Merge lp:~rharding/juju-gui/renderBundle2 into lp:juju-gui/experimental
- renderBundle2
- Merge into trunk
Proposed by
Richard Harding
Status: | Work in progress |
---|---|
Proposed branch: | lp:~rharding/juju-gui/renderBundle2 |
Merge into: | lp:juju-gui/experimental |
Diff against target: |
623 lines (+496/-12) 8 files modified
app/app.js (+1/-0) app/assets/javascripts/d3-components.js (+3/-0) app/modules-debug.js (+3/-0) app/views/topology/bundle.js (+390/-0) app/views/topology/relation.js (+3/-0) app/views/topology/topology.js (+15/-12) test/data/wp-deployer.yaml (+41/-0) test/test_topology.js (+40/-0) |
To merge this branch: | bzr merge lp:~rharding/juju-gui/renderBundle2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+182735@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Madison Scott-Clary (makyo) wrote : | # |
LGTM, QA okay
https:/
File app/views/
https:/
app/views/
s/service/bundle
https:/
File test/test_
https:/
test/test_
passed in params
On 2013/08/28 19:47:30, rharding wrote:
> the
should
Unmerged revisions
- 988. By Richard Harding
-
Sync
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'app/app.js' |
2 | --- app/app.js 2013-08-28 00:28:12 +0000 |
3 | +++ app/app.js 2013-08-28 19:30:33 +0000 |
4 | @@ -1507,6 +1507,7 @@ |
5 | 'FileSaver', |
6 | 'juju-inspector-widget', |
7 | 'juju-ghost-inspector', |
8 | + 'juju-view-bundle', |
9 | 'viewmode-controls' |
10 | ] |
11 | }); |
12 | |
13 | === modified file 'app/assets/javascripts/d3-components.js' |
14 | --- app/assets/javascripts/d3-components.js 2013-08-08 22:36:30 +0000 |
15 | +++ app/assets/javascripts/d3-components.js 2013-08-28 19:30:33 +0000 |
16 | @@ -132,6 +132,7 @@ |
17 | |
18 | modEvents = module.events; |
19 | this.events[module.name] = modEvents; |
20 | + |
21 | this.bind(module.name); |
22 | module.componentBound(); |
23 | |
24 | @@ -286,6 +287,7 @@ |
25 | * @method bind |
26 | **/ |
27 | bind: function(moduleName) { |
28 | + if (this.get('interactive') === false) { return; } |
29 | var eventSet = this.events, |
30 | filtered = {}; |
31 | |
32 | @@ -319,6 +321,7 @@ |
33 | owns = Y.Object.owns, |
34 | module; |
35 | |
36 | + if (this.get('interactive') === false) { return; } |
37 | if (!modEvents || !modEvents.d3) { |
38 | return; |
39 | } |
40 | |
41 | === modified file 'app/modules-debug.js' |
42 | --- app/modules-debug.js 2013-08-19 15:53:41 +0000 |
43 | +++ app/modules-debug.js 2013-08-28 19:30:33 +0000 |
44 | @@ -226,6 +226,9 @@ |
45 | fullpath: '/juju-ui/views/topology/topology.js' |
46 | }, |
47 | |
48 | + 'juju-view-bundle': { |
49 | + fullpath: '/juju-ui/views/topology/bundle.js' |
50 | + }, |
51 | 'juju-view-utils': { |
52 | fullpath: '/juju-ui/views/utils.js' |
53 | }, |
54 | |
55 | === added file 'app/views/topology/bundle.js' |
56 | --- app/views/topology/bundle.js 1970-01-01 00:00:00 +0000 |
57 | +++ app/views/topology/bundle.js 2013-08-28 19:30:33 +0000 |
58 | @@ -0,0 +1,390 @@ |
59 | +/* |
60 | +This file is part of the Juju GUI, which lets users view and manage Juju |
61 | +environments within a graphical interface (https://launchpad.net/juju-gui). |
62 | +Copyright (C) 2012-2013 Canonical Ltd. |
63 | + |
64 | +This program is free software: you can redistribute it and/or modify it under |
65 | +the terms of the GNU Affero General Public License version 3, as published by |
66 | +the Free Software Foundation. |
67 | + |
68 | +This program is distributed in the hope that it will be useful, but WITHOUT |
69 | +ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
70 | +SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero |
71 | +General Public License for more details. |
72 | + |
73 | +You should have received a copy of the GNU Affero General Public License along |
74 | +with this program. If not, see <http://www.gnu.org/licenses/>. |
75 | +*/ |
76 | + |
77 | +'use strict'; |
78 | + |
79 | +/** |
80 | + * Provide the BundleTopology class. |
81 | + * |
82 | + * @module views |
83 | + * @submodule views.BundleTopology |
84 | + */ |
85 | + |
86 | +YUI.add('juju-view-bundle', function(Y) { |
87 | + |
88 | + var juju = Y.namespace('juju'), |
89 | + views = Y.namespace('juju.views'), |
90 | + utils = Y.namespace('juju.views.utils'), |
91 | + models = Y.namespace('juju.models'), |
92 | + d3ns = Y.namespace('d3'), |
93 | + topoUtils = Y.namespace('juju.topology.utils'); |
94 | + |
95 | + /** |
96 | + Manage service rendering and events. |
97 | + |
98 | + @class BundleModule |
99 | + */ |
100 | + |
101 | + var BundleModule = Y.Base.create('BundleModule', d3ns.Module, [], { |
102 | + /** |
103 | + Sync view models with current db.models. |
104 | + |
105 | + @method updateData |
106 | + */ |
107 | + updateData: function() { |
108 | + //model data |
109 | + var topo = this.get('component'); |
110 | + var vis = topo.vis; |
111 | + var db = topo.get('db'); |
112 | + var store = topo.get('store'); |
113 | + |
114 | + var visibleServices = db.services.visible(); |
115 | + views.toBoundingBoxes(this, visibleServices, topo.service_boxes, store); |
116 | + // Break a reference cycle that results in uncollectable objects leaking. |
117 | + visibleServices.reset(); |
118 | + |
119 | + // Nodes are mapped by modelId tuples. |
120 | + this.node = vis.selectAll('.service') |
121 | + .data(Y.Object.values(topo.service_boxes), |
122 | + function(d) {return d.modelId;}); |
123 | + }, |
124 | + |
125 | + |
126 | + /** |
127 | + Attempt to reuse as much of the existing graph and view models |
128 | + as possible to re-render the graph. |
129 | + |
130 | + @method update |
131 | + */ |
132 | + update: function() { |
133 | + var self = this, |
134 | + topo = this.get('component'), |
135 | + width = topo.get('width'), |
136 | + height = topo.get('height'); |
137 | + |
138 | + //Process any changed data. |
139 | + this.updateData(); |
140 | + |
141 | + // Generate a node for each service, draw it as a rect with |
142 | + // labels for service and charm. |
143 | + var node = this.node; |
144 | + |
145 | + // enter |
146 | + node |
147 | + .enter().append('g') |
148 | + .attr({ |
149 | + 'class': function(d) { |
150 | + return (d.subordinate ? 'subordinate ' : '') + |
151 | + (d.pending ? 'pending ' : '') + 'service'; |
152 | + }, |
153 | + 'transform': function(d) { return d.translateStr;}}) |
154 | + .call(self.createServiceNode, self); |
155 | + |
156 | + // Update all nodes. |
157 | + self.updateServiceNodes(node); |
158 | + }, |
159 | + |
160 | + /** |
161 | + Fill a service node with empty structures that will be filled out |
162 | + in the update stage. |
163 | + |
164 | + @param {object} node the node to construct. |
165 | + @param {object} self reference to the view instance. |
166 | + @return {null} side effects only. |
167 | + @method createServiceNode |
168 | + */ |
169 | + createServiceNode: function(node, self) { |
170 | + node.append('image') |
171 | + .classed('service-icon', true) |
172 | + .attr({ |
173 | + 'xlink:href': function(d) { |
174 | + return d.icon; |
175 | + }, |
176 | + width: 96, |
177 | + height: 96 |
178 | + }); |
179 | + node.append('text').append('tspan') |
180 | + .attr('class', 'name') |
181 | + .text(function(d) {return d.displayName; }); |
182 | + }, |
183 | + |
184 | + /** |
185 | + Fill the empty structures within a service node such that they |
186 | + match the db. |
187 | + |
188 | + @param {object} node the collection of nodes to update. |
189 | + @return {null} side effects only. |
190 | + @method updateServiceNodes |
191 | + */ |
192 | + updateServiceNodes: function(node) { |
193 | + if (node.empty()) { |
194 | + return; |
195 | + } |
196 | + var self = this, |
197 | + topo = this.get('component'), |
198 | + landscape = topo.get('landscape'); |
199 | + |
200 | + // Apply Position Annotations |
201 | + // This is done after the services_boxes |
202 | + // binding as the event handler will |
203 | + // use that index. |
204 | + node.each(function(d) { |
205 | + var service = d.model, |
206 | + annotations = service.get('annotations'), |
207 | + x, y; |
208 | + |
209 | + if (!annotations) { |
210 | + return; |
211 | + } |
212 | + |
213 | + // If there are x/y annotations on the service model and they are |
214 | + // different from the node's current x/y coordinates, update the |
215 | + // node, as the annotations may have been set in another session. |
216 | + x = annotations['gui-x']; |
217 | + y = annotations['gui-y']; |
218 | + if (!d || |
219 | + (x !== undefined && x !== d.x) || |
220 | + (y !== undefined && y !== d.y)) { |
221 | + d.x = x; |
222 | + d.y = y; |
223 | + d3.select(this).attr({ |
224 | + x: x, |
225 | + y: y, |
226 | + transform: d.translateStr}); |
227 | + |
228 | + }}); |
229 | + |
230 | + // Mark subordinates as such. This is needed for when a new service |
231 | + // is created. |
232 | + node.filter(function(d) { |
233 | + return d.subordinate; |
234 | + }).classed('subordinate', true); |
235 | + |
236 | + // Size the node for drawing. |
237 | + node.attr({ |
238 | + 'width': function(box) { box.w = 96; return box.w;}, |
239 | + 'height': function(box) { box.h = 96; return box.h;} |
240 | + }); |
241 | + |
242 | + // Draw a subordinate relation indicator. |
243 | + var subRelationIndicator = node.filter(function(d) { |
244 | + return d.subordinate && |
245 | + d3.select(this) |
246 | + .select('.sub-rel-block').empty(); |
247 | + }) |
248 | + .append('g') |
249 | + .attr('class', 'sub-rel-block') |
250 | + .attr('transform', function(d) { |
251 | + // Position the block so that the relation indicator will |
252 | + // appear at the right connector. |
253 | + return 'translate(' + [d.w, d.h / 2 - 26] + ')'; |
254 | + }); |
255 | + |
256 | + subRelationIndicator.append('image') |
257 | + .attr({'xlink:href': '/juju-ui/assets/svgs/sub_relation.svg', |
258 | + 'width': 87, |
259 | + 'height': 47}); |
260 | + subRelationIndicator.append('text').append('tspan') |
261 | + .attr({'class': 'sub-rel-count', |
262 | + 'x': 64, |
263 | + 'y': 47 * 0.8}); |
264 | + |
265 | + // The following are sizes in pixels of the SVG assets used to |
266 | + // render a service, and are used to in calculating the vertical |
267 | + // positioning of text down along the service block. |
268 | + var service_height = 224, |
269 | + name_size = 22, |
270 | + charm_label_size = 16, |
271 | + name_padding = 26, |
272 | + charm_label_padding = 150; |
273 | + |
274 | + node.select('.name') |
275 | + .attr({'style': function(d) { |
276 | + // Programmatically size the font. |
277 | + // Number derived from service assets: |
278 | + // font-size 22px when asset is 224px. |
279 | + return 'font-size:' + d.h * |
280 | + (name_size / service_height) + 'px'; |
281 | + }, |
282 | + 'x': function(d) { return d.w / 2; }, |
283 | + 'y': function(d) { |
284 | + // Number derived from service assets: |
285 | + // padding-top 26px when asset is 224px. |
286 | + return d.h * (name_padding / service_height) + d.h * |
287 | + (name_size / service_height) / 2; |
288 | + } |
289 | + }); |
290 | + |
291 | + // Show whether or not the service is exposed using an indicator. |
292 | + var exposed = node.filter(function(d) { |
293 | + return d.exposed; |
294 | + }); |
295 | + exposed.each(function(d) { |
296 | + var existing = Y.one(this).one('.exposed-indicator'); |
297 | + if (!existing) { |
298 | + existing = d3.select(this).append('image') |
299 | + .attr({'class': 'exposed-indicator on', |
300 | + 'xlink:href': '/juju-ui/assets/svgs/exposed.svg', |
301 | + 'width': 32, |
302 | + 'height': 32 |
303 | + }) |
304 | + .append('title') |
305 | + .text(function(d) { |
306 | + return d.exposed ? 'Exposed' : ''; |
307 | + }); |
308 | + } |
309 | + existing = d3.select(this).select('.exposed-indicator') |
310 | + .attr({ |
311 | + 'x': 145, |
312 | + 'y': 79 |
313 | + }); |
314 | + }); |
315 | + }, |
316 | + |
317 | + |
318 | + /** |
319 | + Pans the environment view to the center all the services on the canvas. |
320 | + |
321 | + @method panToCenter |
322 | + @param {object} evt The event fired. |
323 | + @return {undefined} Side effects only. |
324 | + */ |
325 | + panToCenter: function(evt) { |
326 | + var topo = this.get('component'); |
327 | + var vertices = topoUtils.serviceBoxesToVertices(topo.service_boxes); |
328 | + this.findAndSetCentroid(vertices); |
329 | + }, |
330 | + |
331 | + /** |
332 | + Given a set of vertices, find the centroid and pan to that location. |
333 | + |
334 | + @method findAndSetCentroid |
335 | + @param {array} vertices A list of vertices in the form [x, y]. |
336 | + @return {undefined} Side effects only. |
337 | + */ |
338 | + findAndSetCentroid: function(vertices) { |
339 | + var topo = this.get('component'), |
340 | + centroid = topoUtils.centroid(vertices); |
341 | + // The centroid is set on the topology object due to the fact that it is |
342 | + // used as a sigil to tell whether or not to pan to the point after the |
343 | + // first delta. |
344 | + topo.centroid = centroid; |
345 | + topo.fire('panToPoint', {point: topo.centroid}); |
346 | + } |
347 | + }, { |
348 | + ATTRS: { |
349 | + /** |
350 | + @property {d3ns.Component} component |
351 | + */ |
352 | + component: {} |
353 | + } |
354 | + }); |
355 | + views.BundleModule = BundleModule; |
356 | + |
357 | + /** |
358 | + Display a Bundle using an internal topology. |
359 | + |
360 | + @class BundleTopology |
361 | + */ |
362 | + function BundleTopology(options) { |
363 | + // Options and Init |
364 | + var self = this; |
365 | + options = options || {}; |
366 | + this.options = options; |
367 | + this._cleanups = []; |
368 | + this.db = options.db; |
369 | + if (!this.db) { |
370 | + this.db = new models.Database(); |
371 | + this._cleanups.push(this.db.destroy); |
372 | + } |
373 | + this.store = options.store; |
374 | + if (!this.store) { |
375 | + this.store = new juju.CharmWorld2({}); |
376 | + this._cleanups.push(this.store.destroy); |
377 | + } |
378 | + this.container = options.container; |
379 | + if (!this.container) { |
380 | + this.container = Y.Node.create('<div>'); |
381 | + this.container.addClass('topology-canvas'); |
382 | + this._cleanups.push(function() { |
383 | + self.container.remove(true); |
384 | + }); |
385 | + } |
386 | + |
387 | + var topo = this.topology = new views.Topology(); |
388 | + topo.setAttrs(Y.mix(options, { |
389 | + interactive: false, |
390 | + container: this.container, |
391 | + db: this.db, |
392 | + store: this.store |
393 | + }, true)); |
394 | + |
395 | + // Service view doesn't support Level Of Detail views. |
396 | + // BundleModule provides an icon centric view |
397 | + // of services till service module can support this directly. |
398 | + topo.addModule(views.BundleModule); |
399 | + topo.addModule(views.RelationModule); |
400 | + topo.addModule(views.PanZoomModule); |
401 | + } |
402 | + |
403 | + BundleTopology.prototype.centerViewport = function(scale) { |
404 | + this.topology.modules.PanZoomModule._fire_zoom(scale); |
405 | + // Pan to the centroid of it all after the zoom |
406 | + this.panToCenter(); |
407 | + return this; |
408 | + }; |
409 | + |
410 | + /** |
411 | + Pans the canvas to the center all the services. |
412 | + |
413 | + @method panToCenter |
414 | + @param {object} evt The event fired. |
415 | + @return {undefined} Side effects only. |
416 | + */ |
417 | + BundleTopology.prototype.panToCenter = function() { |
418 | + var topo = this.topology; |
419 | + var vertices = topoUtils.serviceBoxesToVertices(topo.service_boxes); |
420 | + var centroid = topoUtils.centroid(vertices); |
421 | + this.topology.modules.PanZoomModule.panToPoint({point: centroid}); |
422 | + }; |
423 | + |
424 | + |
425 | + BundleTopology.prototype.render = function() { |
426 | + this.topology.render(); |
427 | + this.centerViewport(0.66); |
428 | + return this; |
429 | + }; |
430 | + |
431 | + BundleTopology.prototype.destroy = function() { |
432 | + this._cleanups.forEach(function(cleanupFunc) { |
433 | + cleanupFunc(); |
434 | + }); |
435 | + }; |
436 | + |
437 | + views.BundleTopology = BundleTopology; |
438 | + |
439 | +}, '0.1.0', { |
440 | + requires: [ |
441 | + 'd3', |
442 | + 'd3-components', |
443 | + 'juju-charm-store', |
444 | + 'juju-models', |
445 | + 'juju-topology', |
446 | + 'juju-view-utils' |
447 | + ] |
448 | +}); |
449 | |
450 | === modified file 'app/views/topology/relation.js' |
451 | --- app/views/topology/relation.js 2013-07-03 17:35:21 +0000 |
452 | +++ app/views/topology/relation.js 2013-08-28 19:30:33 +0000 |
453 | @@ -178,6 +178,9 @@ |
454 | var db = topo.get('db'); |
455 | var self = this; |
456 | var relations = db.relations.toArray(); |
457 | + if (!relations || relations.length === 0) { |
458 | + return; |
459 | + } |
460 | this.relations = this.decorateRelations(relations); |
461 | this.updateLinks(); |
462 | this.updateSubordinateRelationsCount(); |
463 | |
464 | === modified file 'app/views/topology/topology.js' |
465 | --- app/views/topology/topology.js 2013-05-17 14:51:05 +0000 |
466 | +++ app/views/topology/topology.js 2013-08-28 19:30:33 +0000 |
467 | @@ -49,17 +49,15 @@ |
468 | */ |
469 | var Topology = Y.Base.create('Topology', d3ns.Component, [], { |
470 | initializer: function(options) { |
471 | - Topology.superclass.constructor.apply(this, arguments); |
472 | - this.options = Y.mix(options || { |
473 | + this.options = Y.mix(options || {}, { |
474 | minZoom: 0.25, |
475 | maxZoom: 2, |
476 | minSlider: 25, |
477 | maxSlider: 200 |
478 | }); |
479 | - |
480 | + Topology.superclass.constructor.apply(this, arguments); |
481 | // Build a service.id -> BoundingBox map for services. |
482 | this.service_boxes = {}; |
483 | - |
484 | this._subscriptions = []; |
485 | }, |
486 | |
487 | @@ -111,11 +109,16 @@ |
488 | this.computeScales(); |
489 | |
490 | // Set up the visualization with a pack layout. |
491 | - svg = d3.select(container.getDOMNode()) |
492 | - .selectAll('.topology-canvas') |
493 | - .append('svg:svg') |
494 | - .attr('width', width) |
495 | - .attr('height', height); |
496 | + var canvas = d3.select(container.getDOMNode()); |
497 | + var base = canvas.select('.topology-canvas'); |
498 | + if (base.empty()) { |
499 | + base = canvas.append('div') |
500 | + .classed('topology-canvas', true); |
501 | + } |
502 | + |
503 | + svg = base.append('svg:svg') |
504 | + .attr('width', width) |
505 | + .attr('height', height); |
506 | this.svg = svg; |
507 | |
508 | this.zoomPlane = svg.append('rect') |
509 | @@ -209,9 +212,9 @@ |
510 | getter: function() {return this.get('size')[1];} |
511 | }, |
512 | /* |
513 | - * Scale and translate are managed by an external module |
514 | - * (PanZoom in this case). If that module isn't |
515 | - * loaded nothing will modify these values. |
516 | + Scale and translate are managed by an external module |
517 | + (PanZoom in this case). If that module isn't |
518 | + loaded nothing will modify these values. |
519 | */ |
520 | scale: { |
521 | getter: function() {return this.zoom.scale();}, |
522 | |
523 | === added file 'test/data/wp-deployer.yaml' |
524 | --- test/data/wp-deployer.yaml 1970-01-01 00:00:00 +0000 |
525 | +++ test/data/wp-deployer.yaml 2013-08-28 19:30:33 +0000 |
526 | @@ -0,0 +1,41 @@ |
527 | +envExport: |
528 | + series: precise |
529 | + services: |
530 | + mysql: |
531 | + charm: "cs:precise/mysql-27" |
532 | + num_units: 1 |
533 | + options: |
534 | + "binlog-format": MIXED |
535 | + "block-size": "5" |
536 | + "dataset-size": "80%" |
537 | + flavor: distro |
538 | + 'gui-x': 50 |
539 | + 'gui-y': 50 |
540 | + "ha-bindiface": eth0 |
541 | + "ha-mcastport": "5411" |
542 | + "max-connections": "-1" |
543 | + "preferred-storage-engine": InnoDB |
544 | + "query-cache-size": "-1" |
545 | + "query-cache-type": "OFF" |
546 | + "rbd-name": mysql1 |
547 | + "tuning-level": safest |
548 | + vip: "" |
549 | + vip_cidr: "24" |
550 | + vip_iface: eth0 |
551 | + annotations: |
552 | + "gui-x": 115 |
553 | + "gui-y": 89 |
554 | + wordpress: |
555 | + charm: "cs:precise/wordpress-16" |
556 | + num_units: 1 |
557 | + options: |
558 | + debug: "no" |
559 | + engine: nginx |
560 | + tuning: single |
561 | + "wp-content": "" |
562 | + annotations: |
563 | + "gui-x": 510 |
564 | + "gui-y": 184 |
565 | + relations: |
566 | + - - "wordpress:db" |
567 | + - "mysql:db" |
568 | |
569 | === modified file 'test/test_topology.js' |
570 | --- test/test_topology.js 2013-06-05 13:27:07 +0000 |
571 | +++ test/test_topology.js 2013-08-28 19:30:33 +0000 |
572 | @@ -28,6 +28,7 @@ |
573 | Y = YUI(GlobalConfig).use(['juju-topology', |
574 | 'd3-components', |
575 | 'juju-tests-utils', |
576 | + 'juju-view-bundle', |
577 | 'node', |
578 | 'node-event-simulate'], |
579 | function(Y) { |
580 | @@ -111,4 +112,43 @@ |
581 | Y.Lang.isValue(topo.vis).should.equal(true); |
582 | }); |
583 | |
584 | + it('should be able to create a bundle display topology', function(done) { |
585 | + container.destroy(true); |
586 | + container = utils.makeContainer(); |
587 | + |
588 | + db = new models.Database(); |
589 | + db.environment.set('defaultSeries', 'precise'); |
590 | + var fakeStore = utils.makeFakeStore(db.charms); |
591 | + fakeStore.iconpath = function() { return 'fake.svg'; }; |
592 | + |
593 | + db.importDeployer( |
594 | + jsyaml.safeLoad(utils.loadFixture('data/wp-deployer.yaml')), |
595 | + fakeStore, {useGhost: true, targetBundle: 'wordpress-prod'}) |
596 | + .then(function() { |
597 | + // Init the topo with the db at this point and ... |
598 | + var bundle = new views.BundleTopology({ |
599 | + container: container, |
600 | + size: [320, 240], |
601 | + db: db, |
602 | + store: fakeStore}).render(); |
603 | + |
604 | + // The size of the element shoul reflect teh passed in params |
605 | + var svg = d3.select(container.getDOMNode()).select('svg'); |
606 | + assert.equal(svg.attr('width'), 320); |
607 | + assert.equal(svg.attr('height'), 240); |
608 | + |
609 | + // We should have the two rendered services |
610 | + assert.equal(container.all('.service').size(), 2); |
611 | + var service = svg.select('.service'); |
612 | + assert.equal(service.attr('width'), '96'); |
613 | + assert.equal(service.attr('transform'), 'translate(115,89)'); |
614 | + // and the one relation between them |
615 | + assert.equal(container.all('.relation').size(), 1); |
616 | + |
617 | + container.remove(true); |
618 | + bundle.destroy(); |
619 | + done(); |
620 | + }).then(undefined, done); |
621 | + }); |
622 | + |
623 | }); |
Reviewers: mp+182735_ code.launchpad. net,
Message:
In general this looks good. Couple of nit-picks below. I'm not
comfortable with so much code dupe between bundle.js and service.js
though so not ok'ing all the way.
https:/ /codereview. appspot. com/13361043/ diff/1/ app/assets/ javascripts/ d3-components. js javascripts/ d3-components. js (right):
File app/assets/
https:/ /codereview. appspot. com/13361043/ diff/1/ app/assets/ javascripts/ d3-components. js#newcode290 javascripts/ d3-components. js:290: if (this.get( 'interactive' )
app/assets/
=== false) { return; }
what is the interactive attr? I don't see it defined and not following
what this is doing.
https:/ /codereview. appspot. com/13361043/ diff/1/ app/views/ topology/ bundle. js topology/ bundle. js (right):
File app/views/
https:/ /codereview. appspot. com/13361043/ diff/1/ app/views/ topology/ bundle. js#newcode80 topology/ bundle. js:80: //Process any changed data.
app/views/
space after the //
https:/ /codereview. appspot. com/13361043/ diff/1/ app/views/ topology/ bundle. js#newcode87 topology/ bundle. js:87: // enter
app/views/
enter? more or remove?
https:/ /codereview. appspot. com/13361043/ diff/1/ app/views/ topology/ bundle. js#newcode92 topology/ bundle. js:92: return (d.subordinate ? 'subordinate '
app/views/
: '') +
so is this repeated? Can it be shared? A typology.utils helper or
something?
https:/ /codereview. appspot. com/13361043/ diff/1/ test/test_ topology. js topology. js (right):
File test/test_
https:/ /codereview. appspot. com/13361043/ diff/1/ test/test_ topology. js#newcode135 topology. js:135: // The size of the element shoul reflect teh
test/test_
passed in params
the
Description:
WIP
https:/ /code.launchpad .net/~rharding/ juju-gui/ renderBundle2/ +merge/ 182735
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/13361043/
Affected files: javascripts/ d3-components. js debug.js topology/ bundle. js topology/ relation. js topology/ topology. js wp-deployer. yaml topology. js
A [revision details]
M app/app.js
M app/assets/
M app/modules-
A app/views/
M app/views/
M app/views/
A test/data/
M test/test_