Merge lp:~bcsaller/juju-gui/viewmodel-improvements into lp:juju-gui/experimental

Proposed by Benjamin Saller
Status: Merged
Merged at revision: 363
Proposed branch: lp:~bcsaller/juju-gui/viewmodel-improvements
Merge into: lp:juju-gui/experimental
Diff against target: 1267 lines (+444/-455)
8 files modified
app/views/service.js (+0/-1)
app/views/topology/relation.js (+2/-2)
app/views/topology/service.js (+23/-42)
app/views/utils.js (+230/-177)
test/test_environment_view.js (+58/-50)
test/test_service_module.js (+14/-0)
test/test_topology.js (+0/-79)
undocumented (+117/-104)
To merge this branch: bzr merge lp:~bcsaller/juju-gui/viewmodel-improvements
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+145755@code.launchpad.net

Description of the change

View model improvements

Change BoundingBoxes to retain reference to module. This allows box models
to directly resolve the db.Model backing them. This also allow access to the
DOMNode in the canvas directly. Certain features of box directly depend on
methods being available in the passed in module, mocking this is still
possible and shown in tests.

https://codereview.appspot.com/7231067/

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote :

Reviewers: mp+145755_code.launchpad.net,

Message:
Please take a look.

Description:
View model improvements

Change BoundingBoxes to retain reference to module. This allows box
models
to directly resolve the db.Model backing them. This also allow access to
the
DOMNode in the canvas directly. Certain features of box directly depend
on
methods being available in the passed in module, mocking this is still
possible and shown in tests.

https://code.launchpad.net/~bcsaller/juju-gui/viewmodel-improvements/+merge/145755

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M app/views/service.js
   M app/views/topology/service.js
   M app/views/utils.js
   M test/test_environment_view.js
   M test/test_service_module.js
   M test/test_topology.js
   M undocumented

Revision history for this message
Gary Poster (gary) wrote :

Hey Ben. I was just skimming this and admiring it in a pre-review kind of way when I noticed that there's a conflict in test/test_service_module.js. Could you resolve it before anyone digs in?

Thanks

Gary

353. By Benjamin Saller

merge trunk

Revision history for this message
Nicola Larosa (teknico) wrote :

Land with changes.

This looks like a nice refactoring. Thanks for regenerating the
"undocumented" file. Please have a look at the comments below.

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js
File app/views/topology/service.js (right):

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode102
app/views/topology/service.js:102: serviceDblClick: function(d, self) {
A more descriptive name than "d" on these methods would be nice, some
day.

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode902
app/views/topology/service.js:902: destroyServiceConfirm: function(m,
view) {
Ditto for "m". Can we do away with these one-letter names, anyway? They
hamper readability.

https://codereview.appspot.com/7231067/diff/1/app/views/utils.js
File app/views/utils.js (right):

https://codereview.appspot.com/7231067/diff/1/app/views/utils.js#newcode611
app/views/utils.js:611: * Utility class that encapsulates Y.Models and
keeps their position
It is not a class anymore, is it? Can this comment be converted to a YUI
comment block somehow?

https://codereview.appspot.com/7231067/diff/1/app/views/utils.js#newcode861
app/views/utils.js:861: * @param {ServiceModule} Module holding box
canvas and context.
s/Module/module/

https://codereview.appspot.com/7231067/diff/1/app/views/utils.js#newcode865
app/views/utils.js:865: **/
Add a "@method toBoundingBoxes" directive, and remove one asterisk from
the block closing mark.

https://codereview.appspot.com/7231067/diff/1/app/views/utils.js#newcode889
app/views/utils.js:889: * relation.getAttrs() plus "source", "target",
and other convenience data.
Indent the text, to show that it continues from the line above.

https://codereview.appspot.com/7231067/diff/1/test/test_environment_view.js
File test/test_environment_view.js (right):

https://codereview.appspot.com/7231067/diff/1/test/test_environment_view.js#newcode846
test/test_environment_view.js:846: // update using pos
s/pos/position/ ?

https://codereview.appspot.com/7231067/diff/1/test/test_environment_view.js#newcode887
test/test_environment_view.js:887:
boxes.wordpress.id.should.equal('wordpress');
Nicer. :-)

https://codereview.appspot.com/7231067/

Revision history for this message
Nicola Larosa (teknico) wrote :

Uhm, I should mention that while "make test-prod" completes
successfully, in "make test-debug" mocha times out after setup, right
before running the tests. "make test-debug" does complete successfully
in trunk, here.

https://codereview.appspot.com/7231067/

354. By Benjamin Saller

the check is at the level of the menu, not the destroy action

355. By Benjamin Saller

lint

Revision history for this message
Benjamin Saller (bcsaller) wrote :
Revision history for this message
Gary Poster (gary) wrote :

I was in the middle of a review--with a long hiatus for various calls
and other responsibilities--when you made the new version. These
comments are from the previous revision, so they may or may not still be
pertinent. I'm sending them off and will start a new review after
lunch.

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js
File app/views/topology/service.js (right):

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode102
app/views/topology/service.js:102: serviceDblClick: function(d, self) {
On 2013/01/31 10:44:24, teknico wrote:
> A more descriptive name than "d" on these methods would be nice, some
day.

+1

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode201
app/views/topology/service.js:201: var service = box;
Shouldn't this be box.model? If so, why does it still work? If it is
really ok for this to be the box, why should we call it the service?

Please address this one way or another so it is clear to the reader what
is going on--and, if necessary, tested that the method is doing what we
expect.

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode242
app/views/topology/service.js:242: views.toBoundingBoxes(this,
db.services, this.service_boxes);
So, this mutates something or other? I need to go read it...

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode243
app/views/topology/service.js:243: this.services =
Y.Object.values(this.service_boxes);
Why do we have to stash this?

If we really want this on the module, I'd like it initialized and
explained in the initializer, the way you do with service_boxes.

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode245
app/views/topology/service.js:245: // XXX: containment breaking alias,
do we need this?
If we do, then we should only store service_boxes on topo, and not
locally on "this," right? (No need to change this now, just a fly-by
thought.)

https://codereview.appspot.com/7231067/

Revision history for this message
Madison Scott-Clary (makyo) wrote :

Reviewing this in light of the possible changes to my current branch.
Overall I think it's really good stuff (I like the new box object much
better, as it makes things a little more readable, to me), and will help
me out in SOME respects, but only a few. Thanks for the work. I'll
defer to the others for landability, as they've got good comments.

https://codereview.appspot.com/7231067/

Revision history for this message
Madison Scott-Clary (makyo) wrote :

Increasing the number of units for a service is not reflected in the
environment view (increased WP from 1 to 10; service isn't resized,
health graph remains the same, unit count still says 1). Additionally,
I receive some errors when trying to create relationships between
services. Sometimes, it'll fail out when trying to find available
endpoints, and sometimes it dies on creating a relation. I was trying
with haproxy. After doing so, several things seemed to fail in a
cascade.

https://codereview.appspot.com/7231067/

Revision history for this message
Benjamin Saller (bcsaller) wrote :

On 2013/01/31 19:51:03, matthew.scott wrote:
> Increasing the number of units for a service is not reflected in the
environment
> view (increased WP from 1 to 10; service isn't resized, health graph
remains the
> same, unit count still says 1). Additionally, I receive some errors
when trying
> to create relationships between services. Sometimes, it'll fail out
when trying
> to find available endpoints, and sometimes it dies on creating a
relation. I
> was trying with haproxy. After doing so, several things seemed to
fail in a
> cascade.

You found a pre-existing bug but the fix is a small delta. Trying to
only incrementally render the env view lead to calling update() and
rendered()
on dbchange but we were only doing this when env view was the current
view. This has been changed in the branch to always update the env view
on db change as well as any other actions that need to be taken.

https://codereview.appspot.com/7231067/

Revision history for this message
Gary Poster (gary) wrote :

Hi Ben. Thank you for this very nice branch.

Land with changes, as documented in this review and my previous one.

I asked Matt to look at this as well if he has time. I didn't have time
to look at it during normal hours, but already had some comments waiting
in the wings, so decided to complete my review.

Gary

https://codereview.appspot.com/7231067/diff/10/app/views/topology/relation.js
File app/views/topology/relation.js (right):

https://codereview.appspot.com/7231067/diff/10/app/views/topology/relation.js#newcode779
app/views/topology/relation.js:779: return (relation.source.modelId ===
service.modelId ||
These sorts of changes--change calls to attrs--are throughout the
branch, and nice.

https://codereview.appspot.com/7231067/diff/10/app/views/topology/service.js
File app/views/topology/service.js (right):

https://codereview.appspot.com/7231067/diff/10/app/views/topology/service.js#newcode256
app/views/topology/service.js:256: views.toBoundingBoxes(this,
db.services, this.service_boxes);
 From comment on previous partial review: I now see that
this.service_boxes is mutated. Cool.

https://codereview.appspot.com/7231067/diff/10/app/views/utils.js
File app/views/utils.js (right):

https://codereview.appspot.com/7231067/diff/10/app/views/utils.js#newcode618
app/views/utils.js:618: function positionProp(name) {
Nice.

https://codereview.appspot.com/7231067/diff/10/app/views/utils.js#newcode790
app/views/utils.js:790: getNearestConnector: {
Now that it is a property, wouldn't this be better as a noun,
"nearestConnector"?

https://codereview.appspot.com/7231067/diff/10/app/views/utils.js#newcode817
app/views/utils.js:817: getConnectorPair: {
Likewise: "connectorPair"?

https://codereview.appspot.com/7231067/diff/10/test/test_environment_view.js
File test/test_environment_view.js (right):

https://codereview.appspot.com/7231067/diff/10/test/test_environment_view.js#newcode910
test/test_environment_view.js:910: it('must be able to update boxes with
new model data',
Nice test.

https://codereview.appspot.com/7231067/diff/10/undocumented
File undocumented (right):

https://codereview.appspot.com/7231067/diff/10/undocumented#newcode247
undocumented:247: app/models/charm.js:50 "initializer"
We went from 234 to 247 undocumented functions. It's our policy to try
to keep that number stable or lower even when we regenerate the
"undocumented" file. I reviewed why the number went up, and I see that
it is largely or exclusively because of the property getter and setter
functions in the utils work you did. You documented the properties
quite nicely, so, without a counterargument to convince me otherwise,
this case seems to be one in which our yuidoc linter needs to be
improved. Therefore, I'd like to honor our policy, but I'd ask you to
do it by simply filing a bug against our yuidoc linter describing the
situation, and how the linter could be improved to no longer complain
about this situation. If you'd also like to document 13 random
functions arbitrarily to keep our count down, I won't stop you, but I do
not ask for that and in fact would prefer if that task did not slow
landing the branch down.

https://codereview.appspot.com/7231067/

Revision history for this message
Benjamin Saller (bcsaller) wrote :

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js
File app/views/topology/service.js (right):

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode243
app/views/topology/service.js:243: this.services =
Y.Object.values(this.service_boxes);
On 2013/01/31 17:30:12, gary.poster wrote:
> Why do we have to stash this?

> If we really want this on the module, I'd like it initialized and
explained in
> the initializer, the way you do with service_boxes.

removed.

https://codereview.appspot.com/7231067/diff/1/app/views/topology/service.js#newcode245
app/views/topology/service.js:245: // XXX: containment breaking alias,
do we need this?
On 2013/01/31 17:30:12, gary.poster wrote:
> If we do, then we should only store service_boxes on topo, and not
locally on
> "this," right? (No need to change this now, just a fly-by thought.)
Moved to topo.

https://codereview.appspot.com/7231067/diff/10/app/views/utils.js
File app/views/utils.js (right):

https://codereview.appspot.com/7231067/diff/10/app/views/utils.js#newcode790
app/views/utils.js:790: getNearestConnector: {
On 2013/02/01 02:26:05, gary.poster wrote:
> Now that it is a property, wouldn't this be better as a noun,
> "nearestConnector"?

these two methods take arguments and are not normal properties so they
retain the 'get' prefix.

https://codereview.appspot.com/7231067/

Revision history for this message
Benjamin Saller (bcsaller) wrote :

*** Submitted:

View model improvements

Change BoundingBoxes to retain reference to module. This allows box
models
to directly resolve the db.Model backing them. This also allow access to
the
DOMNode in the canvas directly. Certain features of box directly depend
on
methods being available in the passed in module, mocking this is still
possible and shown in tests.

R=teknico, gary.poster, matthew.scott
CC=
https://codereview.appspot.com/7231067

https://codereview.appspot.com/7231067/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/views/service.js'
2--- app/views/service.js 2013-01-24 19:11:19 +0000
3+++ app/views/service.js 2013-01-31 16:56:20 +0000
4@@ -118,7 +118,6 @@
5 getModelURL = this.get('getModelURL'),
6 db = this.get('db'),
7 unit_names = ev.unit_names;
8- console.log('_removeUnitCallback with: ', arguments);
9
10 if (ev.err) {
11 db.notifications.add(
12
13=== modified file 'app/views/topology/relation.js'
14--- app/views/topology/relation.js 2013-01-30 12:38:03 +0000
15+++ app/views/topology/relation.js 2013-01-31 16:56:20 +0000
16@@ -776,8 +776,8 @@
17 */
18 subordinateRelationsForService: function(service) {
19 return this.relations.filter(function(relation) {
20- return (relation.source.modelId() === service.modelId() ||
21- relation.target.modelId() === service.modelId()) &&
22+ return (relation.source.modelId === service.modelId ||
23+ relation.target.modelId === service.modelId) &&
24 relation.isSubordinate;
25 });
26 },
27
28=== modified file 'app/views/topology/service.js'
29--- app/views/topology/service.js 2013-01-30 12:38:03 +0000
30+++ app/views/topology/service.js 2013-01-31 16:56:20 +0000
31@@ -102,7 +102,7 @@
32 serviceDblClick: function(d, self) {
33 // Just show the service on double-click.
34 var topo = self.get('component'),
35- service = topo.serviceForBox(d);
36+ service = d.model;
37 // The browser sends a click event right before the dblclick one, and it
38 // opens the service menu: close it before moving to the service details.
39 self.service_click_actions.hideServiceMenu(null, self);
40@@ -194,7 +194,7 @@
41 // Get the service element
42 var topo = context.get('component');
43 var box = topo.get('active_service');
44- var service = topo.serviceForBox(box);
45+ var service = box.model;
46 context.service_click_actions
47 .hideServiceMenu(d, context);
48 context.service_click_actions
49@@ -210,7 +210,7 @@
50 // Get the service element
51 var topo = context.get('component');
52 var box = topo.get('active_service');
53- var service = topo.serviceForBox(box);
54+ var service = box;
55 context.service_click_actions
56 .hideServiceMenu(box, context);
57 context.service_click_actions
58@@ -252,31 +252,16 @@
59 var topo = this.get('component');
60 var vis = topo.vis;
61 var db = topo.get('db');
62- var services = db.services.map(views.toBoundingBox);
63-
64- this.services = services;
65-
66- Y.each(services, function(service) {
67- // Update services with existing positions.
68- // In the future it would be better to sync
69- // the model to the existing box.
70- var existing = this.service_boxes[service.id];
71- if (existing) {
72- service.pos = existing.pos;
73- service.inDrag = existing.inDrag;
74- }
75- service.margins(service.subordinate ?
76- this.subordinate_margins :
77- this.service_margins);
78- this.service_boxes[service.id] = service;
79- }, this);
80+
81+ views.toBoundingBoxes(this, db.services, this.service_boxes);
82+ this.services = Y.Object.values(this.service_boxes);
83
84 // XXX: containment breaking alias, do we need this?
85 topo.service_boxes = this.service_boxes;
86
87 // Nodes are mapped by modelId tuples.
88 this.node = vis.selectAll('.service')
89- .data(services, function(d) {return d.modelId();});
90+ .data(this.services, function(d) {return d.modelId;});
91 },
92
93 /**
94@@ -363,7 +348,7 @@
95 }
96
97 selection.attr('transform', function(d, i) {
98- return d.translateStr();
99+ return d.translateStr;
100 });
101 if (topo.get('active_service') === d) {
102 self.updateServiceMenuLocation();
103@@ -443,7 +428,7 @@
104 })
105 .call(this.dragBehavior)
106 .attr('transform', function(d) {
107- return d.translateStr();
108+ return d.translateStr;
109 })
110 .call(self.createServiceNode);
111
112@@ -529,7 +514,7 @@
113 // binding as the event handler will
114 // use that index.
115 node.each(function(d) {
116- var service = topo.serviceForBox(d),
117+ var service = d.model,
118 annotations = service.get('annotations'),
119 x, y;
120
121@@ -667,7 +652,7 @@
122 return d.w / 10 * 7;
123 })
124 .attr('y', function(d) {
125- return d.getRelativeCenter()[1] - (d.w / 6) / 2;
126+ return d.relativeCenter[1] - (d.w / 6) / 2;
127 })
128 .append('title')
129 .text(function(d) {
130@@ -712,7 +697,7 @@
131
132 node.select('.service-status')
133 .attr('transform', function(d) {
134- return 'translate(' + d.getRelativeCenter() + ')';
135+ return 'translate(' + d.relativeCenter + ')';
136 });
137 node.select('.service-health-mask')
138 .attr('width', function(d) {
139@@ -838,7 +823,7 @@
140 var cp_width = cp.getClientRect().width,
141 menu_left = service.x * z + service.w * z / 2 <
142 this.width * z / 2,
143- service_center = service.getRelativeCenter();
144+ service_center = service.relativeCenter;
145 if (menu_left) {
146 cp.removeClass('left')
147 .addClass('right');
148@@ -896,7 +881,7 @@
149 showServiceMenu: function(box, module, context) {
150 var serviceMenu = module.get('container').one('#service-menu');
151 var topo = module.get('component');
152- var service = topo.serviceForBox(box);
153+ var service = box.model;
154
155 if (box && !serviceMenu.hasClass('active')) {
156 topo.set('active_service', box);
157@@ -950,12 +935,12 @@
158 */
159 destroyServiceConfirm: function(m, view) {
160 // Set service in view.
161- view.set('destroy_service', m);
162+ view.set('destroy_service', m.model);
163
164 // Show dialog.
165 view.set('destroy_dialog', views.createModalPanel(
166 'Are you sure you want to destroy the service? ' +
167- 'This cannot be undone.',
168+ 'This cannot be undone.',
169 '#destroy-modal-panel',
170 'Destroy Service',
171 Y.bind(function(ev) {
172@@ -963,9 +948,8 @@
173 var btn = ev.target;
174 btn.set('disabled', true);
175 view.service_click_actions
176- .destroyService(m, view, btn);
177- },
178- this)));
179+ .destroyService(m, view, btn);
180+ }, this)));
181 },
182
183 /*
184@@ -976,15 +960,14 @@
185 destroyService: function(m, view, btn) {
186 var env = view.get('component').get('env'),
187 service = view.get('destroy_service');
188- env.destroy_service(
189- service.get('id'),
190- Y.bind(this._destroyCallback, view,
191- service, view, btn));
192+ env.destroy_service(service.get('id'),
193+ Y.bind(this._destroyCallback, view,
194+ service, view, btn));
195 },
196
197 _destroyCallback: function(service, view, btn, ev) {
198 var getModelURL = view.get('component').get('getModelURL'),
199- db = view.get('component').get('db');
200+ db = view.get('component').get('db');
201 if (ev.err) {
202 db.notifications.add(
203 new models.Notification({
204@@ -993,8 +976,7 @@
205 level: 'error',
206 link: getModelURL(service),
207 modelId: service
208- })
209- );
210+ }));
211 } else {
212 var relations = db.relations.get_relations_for_service(service);
213 Y.each(relations, function(relation) {
214@@ -1007,7 +989,6 @@
215 btn.set('disabled', false);
216 }
217
218-
219 }
220 }, {
221 ATTRS: {}
222
223=== modified file 'app/views/utils.js'
224--- app/views/utils.js 2013-01-24 17:43:57 +0000
225+++ app/views/utils.js 2013-01-31 16:56:20 +0000
226@@ -607,224 +607,277 @@
227 };
228
229
230-
231 /*
232- * Utility class that encapsulates Y.Models and keeps their positional
233+ * Utility class that encapsulates Y.Models and keeps their position
234 * state within an svg canvas.
235 *
236 * As a convenience attributes of the encapsulated model are exposed
237 * directly as attributes.
238 */
239- function BoundingBox() {
240- var x, y, w, h, value, modelId, boxMargins;
241- function Box() {}
242-
243- Box.model = function(_) {
244- if (!arguments.length) {
245- return modelId;
246- }
247- modelId = [_.name, _.get('id')];
248-
249- // Copy all the attrs from model to Box
250- Y.mix(Box, _.getAttrs());
251- return Box;
252- };
253-
254- Box.margins = function(_) {
255- if (!arguments.length) {
256- return boxMargins;
257- }
258- boxMargins = _;
259- return Box;
260- };
261-
262- Object.defineProperties(Box, {
263- pos: {
264- writeable: true,
265- get: function() {
266- return {x: this.x, y: this.y, w: this.w, h: this.h};
267- },
268- set: function(value) {
269- Y.mix(this, value, true, ['x', 'y', 'w', 'h']);
270- }
271- },
272- x: {
273- writeable: true,
274- get: function() { return x;},
275- set: function(value) {
276- this.px = this.x;
277- x = value;
278- return this;}
279- },
280- y: {
281- writeable: true,
282- get: function() { return y;},
283- set: function(value) {
284- this.py = this.y;
285- y = value;
286- return this;
287- }
288- }
289- });
290-
291- Box.getXY = function() {return [this.x, this.y];};
292- Box.getWH = function() {return [this.w, this.h];};
293+ var _box = {};
294+ function positionProp(name) {
295+ return {
296+ get: function() {return this['_' + name];},
297+ set: function(value) {
298+ this['p' + name] = this['_' + name];
299+ this['_' + name] = value;
300+ }
301+ };
302+ }
303+
304+ Object.defineProperties(_box, {
305+ x: positionProp('x'),
306+ y: positionProp('y'),
307+ w: positionProp('w'),
308+ h: positionProp('h'),
309+
310+ pos: {
311+ get: function() { return {x: this.x, y: this.y, w: this.w, h: this.h};},
312+ set: function(value) {
313+ Y.mix(this, value, true, ['x', 'y', 'w', 'h']);
314+ }
315+ },
316+
317+ translateStr: {
318+ get: function() { return 'translate(' + this.x + ',' + this.y + ')';}
319+ },
320+
321+ model: {
322+ get: function() {
323+ if (!this._modelName) { return null;}
324+ return this.topology.serviceForBox(this);
325+ },
326+ set: function(value) {
327+ if (Y.Lang.isValue(value)) {
328+ Y.mix(this, value.getAttrs(), true);
329+ this._modelName = value.name;
330+ }
331+ }
332+ },
333+ modelId: {
334+ get: function() { return this._modelName + '-' + this.id;}
335+ },
336+ node: {
337+ get: function() { return this.module.getServiceNode(this.id);}
338+ },
339+ topology: {
340+ get: function() { return this.module.get('component');}
341+ },
342+ xy: {
343+ get: function() { return [this.x, this.y];}
344+ },
345+ wh: {
346+ get: function() { return [this.w, this.h];}
347+ },
348+
349+ /*
350+ * Extract margins from the supplied module.
351+ */
352+ margins: {
353+ get: function() {
354+ if (!this.module) {
355+ // Used in testing.
356+ return {top: 0, bottom: 0, left: 0, right: 0};
357+ }
358+ if (this.subordinate) {
359+ return this.module.subordinate_margins;
360+ }
361+ return this.module.service_margins;
362+ }
363+ },
364
365 /*
366 * Returns the center of the box with the origin being the upper-left
367 * corner of the box.
368 */
369- Box.getRelativeCenter = function() {
370- var margins = this.margins();
371- return [
372- (this.w / 2) + (margins &&
373- (margins.left * this.w / 2 -
374- margins.right * this.w / 2) || 0),
375- (this.h / 2) - (margins &&
376- (margins.bottom * this.h / 2 -
377- margins.top * this.h / 2) || 0)
378- ];
379- };
380+ relativeCenter: {
381+ get: function() {
382+ var margins = this.margins;
383+ return [
384+ (this.w / 2) + (margins &&
385+ (margins.left * this.w / 2 -
386+ margins.right * this.w / 2) || 0),
387+ (this.h / 2) - (margins &&
388+ (margins.bottom * this.h / 2 -
389+ margins.top * this.h / 2) || 0)
390+ ];}
391+ },
392
393 /*
394 * Returns the absolute center of the box on the canvas.
395 */
396- Box.getCenter = function() {
397- var center = this.getRelativeCenter();
398- center[0] += this.x;
399- center[1] += this.y;
400- return center;
401- };
402-
403+ center: {
404+ get: function() {
405+ var c = this.relativeCenter;
406+ c[0] += this.x;
407+ c[1] += this.y;
408+ return c;
409+ }
410+ },
411
412 /*
413 * Returns true if a given point in the form [x, y] is within the box.
414+ * Transform could be extracted from the topology but the current
415+ * arguments ease testing.
416 */
417- Box.containsPoint = function(point, transform) {
418- transform = transform || {
419- scale: function() { return 1; },
420- translate: function() { return [0, 0]; }
421- };
422- var s = transform.scale(), tr = transform.translate();
423- if (point[0] >= this.x * s + tr[0] &&
424- point[0] <= this.x * s + this.w * s + tr[0] &&
425- point[1] >= this.y * s + tr[1] &&
426- point[1] <= this.y * s + this.h * s + tr[1]) {
427- return true;
428+ containsPoint: {
429+ writable: true, // For test overrides.
430+ configurable: true,
431+ value: function(point, transform) {
432+ transform = transform || {
433+ scale: function() { return 1; },
434+ translate: function() { return [0, 0]; }
435+ };
436+ var tr = transform.translate(),
437+ s = transform.scale();
438+
439+ return (point[0] >= this.x * s + tr[0] &&
440+ point[0] <= this.x * s + this.w * s + tr[0] &&
441+ point[1] >= this.y * s + tr[1] &&
442+ point[1] <= this.y * s + this.h * s + tr[1]);
443 }
444- return false;
445- };
446+ },
447
448 /*
449- * Return the 50% points along each side as xy pairs
450+ * Return the 50% points along each side as [x, y] pairs.
451 */
452- Box.getConnectors = function() {
453- // Since the service nodes have a shadow that takes up a bit of
454- // space on the sides and bottom of the actual node itself, add a bit
455- // of a margin to the actual connecting points. The margin is specified
456- // as a percentage of the width or height, as those are affected by the
457- // scale. This is calculated by taking the distance of the shadow from
458- // the edge of the actual shape and calculating it as a percentage of
459- // the total height of the shape.
460- var margins = this.margins();
461- return {
462- top: [
463- this.x + (this.w / 2),
464- this.y + (margins && (margins.top * this.h) || 0)
465- ],
466- right: [
467- this.x + this.w - (margins && (margins.right * this.w) || 0),
468- this.y + (this.h / 2) - (
469- margins && (margins.bottom * this.h / 2 -
470- margins.top * this.h / 2) || 0)
471- ],
472- bottom: [
473- this.x + (this.w / 2),
474- this.y + this.h - (margins && (margins.bottom * this.h) || 0)
475- ],
476- left: [
477- this.x + (margins && (margins.left * this.w) || 0),
478- this.y + (this.h / 2) - (
479- margins && (margins.bottom * this.h / 2 -
480- margins.top * this.h / 2) || 0)
481- ]
482- };
483- };
484+ connectors: {
485+ get: function() {
486+ // Since the service nodes have a shadow that takes up a bit of
487+ // space on the sides and bottom of the actual node itself, add a bit
488+ // of a margin to the actual connecting points. The margin is specified
489+ // as a percentage of the width or height, as those are affected by the
490+ // scale. This is calculated by taking the distance of the shadow from
491+ // the edge of the actual shape and calculating it as a percentage of
492+ // the total height of the shape.
493+ var margins = this.margins;
494+ return {
495+ top: [
496+ this.x + (this.w / 2),
497+ this.y + (margins && (margins.top * this.h) || 0)
498+ ],
499+ right: [
500+ this.x + this.w - (margins && (margins.right * this.w) || 0),
501+ this.y + (this.h / 2) - (
502+ margins && (margins.bottom * this.h / 2 -
503+ margins.top * this.h / 2) || 0)
504+ ],
505+ bottom: [
506+ this.x + (this.w / 2),
507+ this.y + this.h - (margins && (margins.bottom * this.h) || 0)
508+ ],
509+ left: [
510+ this.x + (margins && (margins.left * this.w) || 0),
511+ this.y + (this.h / 2) - (
512+ margins && (margins.bottom * this.h / 2 -
513+ margins.top * this.h / 2) || 0)
514+ ]
515+ };
516+ }
517+ },
518
519- Box._distance = function(xy1, xy2) {
520- return Math.sqrt(Math.pow(xy1[0] - xy2[0], 2) +
521- Math.pow(xy1[1] - xy2[1], 2));
522- };
523+ _distance: {
524+ value: function(xy1, xy2) {
525+ return Math.sqrt(Math.pow(xy1[0] - xy2[0], 2) +
526+ Math.pow(xy1[1] - xy2[1], 2));
527+ }
528+ },
529
530 /*
531 * Connectors are defined on four borders, find the one closes to
532 * another BoundingBox
533 */
534- Box.getNearestConnector = function(other_box) {
535- var connectors = this.getConnectors(),
536- result = null,
537- shortest_d = Infinity,
538- source = other_box;
539- // duck typing
540- if ('getXY' in other_box) {
541- source = other_box.getXY();
542+ getNearestConnector: {
543+ value: function(box_or_xy) {
544+ var connectors = this.connectors,
545+ result = null,
546+ shortest_d = Infinity,
547+ source = box_or_xy;
548+
549+ if (box_or_xy.xy !== undefined) {
550+ source = box_or_xy.xy;
551+ }
552+ Y.each(connectors, function(ep) {
553+ // Take the distance of each XY pair
554+ var d = this._distance(source, ep);
555+ if (!Y.Lang.isValue(result) || d < shortest_d) {
556+ shortest_d = d;
557+ result = ep;
558+ }
559+ }, this);
560+ return result;
561 }
562-
563- Y.each(connectors, function(ep) {
564- // Take the distance of each XY pair
565- var d = this._distance(source, ep);
566- if (!Y.Lang.isValue(result) || d < shortest_d) {
567- shortest_d = d;
568- result = ep;
569- }
570- }, this);
571- return result;
572- };
573+ },
574
575 /*
576 * Return [this.connector.XY, other.connector.XY] (in that order)
577 * that as nearest to each other. This can be used to define start-end
578 * points for routing.
579 */
580- Box.getConnectorPair = function(other_box) {
581- var sc = Box.getConnectors(),
582- oc = other_box.getConnectors(),
583- result = null,
584- shortest_d = Infinity;
585-
586- Y.each(sc, function(ep1) {
587- Y.each(oc, function(ep2) {
588- // Take the distance of each XY pair
589- var d = this._distance(ep1, ep2);
590- if (!Y.Lang.isValue(result) || d < shortest_d) {
591- shortest_d = d;
592- result = [ep1, ep2];
593- }
594- }, other_box);
595- }, this);
596- return result;
597- };
598-
599- Box.translateStr = function() {
600- return 'translate(' + this.getXY() + ')';
601- };
602-
603- Box.modelId = function() {
604- return modelId[0] + '-' + modelId[1];
605- };
606-
607- return Box;
608+ getConnectorPair: {
609+ value: function(other_box) {
610+ var sc = this.connectors,
611+ oc = other_box.connectors,
612+ result = null,
613+ shortest_d = Infinity;
614+
615+ Y.each(sc, function(ep1) {
616+ Y.each(oc, function(ep2) {
617+ // Take the distance of each XY pair
618+ var d = this._distance(ep1, ep2);
619+ if (!Y.Lang.isValue(result) || d < shortest_d) {
620+ shortest_d = d;
621+ result = [ep1, ep2];
622+ }
623+ }, other_box);
624+ }, this);
625+ return result;
626+ }
627+ }
628+ });
629+
630+ /**
631+ * @method BoundingBox
632+ * @param {Module} module Typically service module.
633+ * @param {Model} model Model object.
634+ * @return {BoundingBox} A Box model.
635+ */
636+ function BoundingBox(module, model) {
637+ var b = Object.create(_box);
638+ b.module = module;
639+ b.model = model;
640+
641+ return b;
642 }
643
644 views.BoundingBox = BoundingBox;
645
646- views.toBoundingBox = function(model) {
647- var box = new BoundingBox();
648- box.model(model);
649- return box;
650+ /**
651+ * Covert an Array of services into BoundingBoxes. If
652+ * existing is supplied it should be a map of {id: box}
653+ * and will be updated in place by merging changed attribute
654+ * into the index.
655+ *
656+ * @param {ServiceModule} Module holding box canvas and context.
657+ * @param {ModelList} services Service modellist.
658+ * @param {Object} existing id:box mapping.
659+ * @return {Object} id:box mapping.
660+ **/
661+ views.toBoundingBoxes = function(module, services, existing) {
662+ var result = existing || {};
663+ Y.each(services, function() {
664+ var id = this.get('id');
665+ if (result[id] !== undefined) {
666+ result[id].model = this;
667+ } else {
668+ result[id] = new BoundingBox(module, this);
669+ }
670+ });
671+ return result;
672 };
673
674
675+
676 /**
677 * Decorate a relation with some related/derived data.
678 *
679@@ -841,9 +894,9 @@
680 source: source,
681 target: target,
682 compositeId: (
683- source.modelId() +
684+ source.modelId +
685 (hasRelations ? ':' + relation.endpoints[0][1].name : '') +
686- '-' + target.modelId() +
687+ '-' + target.modelId +
688 (hasRelations ? ':' + relation.endpoints[1][1].name : ''))
689 };
690 Y.mix(decorated, relation.getAttrs());
691
692=== modified file 'test/test_environment_view.js'
693--- test/test_environment_view.js 2013-01-29 19:12:52 +0000
694+++ test/test_environment_view.js 2013-01-31 16:56:20 +0000
695@@ -789,7 +789,7 @@
696 });
697
698 describe('view model support infrastructure', function() {
699- var Y, views, models;
700+ var Y, views, models, module, service;
701
702 before(function(done) {
703 Y = YUI(GlobalConfig).use(['juju-views', 'juju-models'],
704@@ -800,48 +800,46 @@
705 });
706 });
707
708+ beforeEach(function() {
709+ service = new models.Service({
710+ id: 'mediawiki',
711+ exposed: true});
712+ module = {
713+ topology: {
714+ serviceForBox: function() {return service;}
715+ }};
716+ });
717+
718 it('must be able to get us nearest connectors',
719 function() {
720- var b1 = views.BoundingBox(),
721- b2 = views.BoundingBox();
722+ var b1 = views.BoundingBox(module, service),
723+ b2 = views.BoundingBox(module, service);
724
725- // raw poperty access
726+ // raw property access
727 b1.x = 0; b1.y = 0;
728 b1.w = 100; b1.h = 200;
729
730 // Use pos to set b2
731 b2.pos = {x: 200, y: 300, w: 100, h: 200};
732
733- b1.getXY().should.eql([0, 0]);
734- b2.getWH().should.eql([100, 200]);
735+ b1.xy.should.eql([0, 0]);
736+ b2.wh.should.eql([100, 200]);
737
738- b1.margins({
739- top: 0,
740- bottom: 0,
741- right: 0,
742- left: 0
743- });
744 b1.getNearestConnector([0, 0]);
745
746 b1.getNearestConnector(b2).should
747- .eql(b1.getConnectors().bottom);
748+ .eql(b1.connectors.bottom);
749
750- b2.margins({
751- top: 0,
752- bottom: 0,
753- right: 0,
754- left: 0
755- });
756 b2.getNearestConnector(b1).should
757- .eql(b2.getConnectors().top);
758+ .eql(b2.connectors.top);
759
760 b1.getConnectorPair(b2).should.eql([
761- b1.getConnectors().bottom,
762- b2.getConnectors().top]);
763+ b1.connectors.bottom,
764+ b2.connectors.top]);
765 });
766
767 it('must be able to tell if a point is inside a box', function() {
768- var b = views.BoundingBox();
769+ var b = views.BoundingBox(module, service);
770 b.pos = {x: 100, y: 100, w: 50, h: 50};
771
772 b.containsPoint([125, 125]).should.equal(true);
773@@ -850,10 +848,10 @@
774
775 it('must be able to save and restore old position information',
776 function() {
777- var b1 = views.BoundingBox(),
778- b2 = views.BoundingBox();
779+ var b1 = views.BoundingBox(module, service),
780+ b2 = views.BoundingBox(module, service);
781
782- // raw poperty access
783+ // raw property access
784 b1.x = 0; b1.y = 0;
785 b1.w = 100; b1.h = 200;
786
787@@ -872,13 +870,10 @@
788
789 });
790
791- it('must be able to access model attributes easily', function() {
792- var service = new models.Service({id: 'mediawiki',
793- exposed: true}),
794- b1 = new views.BoundingBox();
795- b1.model(service);
796+ it('must be able to access model attributes', function() {
797+ var b1 = new views.BoundingBox(module, service);
798
799- b1.modelId().should.equal('service-mediawiki');
800+ b1.modelId.should.equal('service-mediawiki');
801
802 // properties of the model have mapped to the box
803 b1.id.should.equal('mediawiki');
804@@ -887,36 +882,49 @@
805
806 it('must be able to update position data and not touch model data',
807 function() {
808- var service = new models.Service({id: 'mediawiki',
809- exposed: true}),
810- b1 = new views.BoundingBox();
811- b1.model(service);
812+ var b1 = views.BoundingBox(module, service);
813 b1.x = 0; b1.y = 0;
814 b1.w = 100; b1.h = 200;
815 b1.id.should.equal('mediawiki');
816
817 // X/Y updated, other keys ignored
818- b1.pos = {x: 100, y: 100, id: 'mediawiki'};
819+ b1.pos = {x: 100, y: 100, id: 'blubber'};
820 b1.x.should.equal(100);
821 b1.id.should.equal('mediawiki');
822-
823 });
824
825 it('must be able to map from sequence of models to boundingboxes',
826 function() {
827 var services = new models.ServiceList();
828 services.add([{id: 'mysql'},
829- {id: 'haproxy'},
830- {id: 'memcache'},
831- {id: 'wordpress'}]);
832-
833- services.size().should.equal(4);
834- var boxes = services.map(views.toBoundingBox);
835- boxes.length.should.equal(4);
836- boxes[0].id.should.equal('mysql');
837- boxes[3].id.should.equal('wordpress');
838- });
839-
840+ {id: 'haproxy'},
841+ {id: 'memcache'},
842+ {id: 'wordpress'}]);
843+
844+ services.size().should.equal(4);
845+ var boxes = views.toBoundingBoxes(module, services);
846+ boxes.mysql.id.should.equal('mysql');
847+ boxes.wordpress.id.should.equal('wordpress');
848+ });
849+
850+ it('must be able to update boxes with new model data',
851+ function() {
852+ var services = new models.ServiceList();
853+ services.add([{id: 'mysql', exposed: false},
854+ {id: 'haproxy'},
855+ {id: 'memcache'},
856+ {id: 'wordpress'}]);
857+
858+ services.size().should.equal(4);
859+ var boxes = views.toBoundingBoxes(module, services);
860+ var mysql = services.getById('mysql');
861+
862+ boxes.mysql.exposed.should.equal(false);
863+ mysql.set('exposed', true);
864+
865+ // The third argument here implies an update.
866+ views.toBoundingBoxes(module, services, boxes);
867+ boxes.mysql.exposed.should.equal(true);
868+ });
869 });
870-
871 })();
872
873=== modified file 'test/test_service_module.js'
874--- test/test_service_module.js 2013-01-29 18:59:37 +0000
875+++ test/test_service_module.js 2013-01-31 16:56:20 +0000
876@@ -232,6 +232,20 @@
877 cancelButton.simulate('click');
878 });
879
880+ it('should prevent the Juju GUI service from being destroyed', function() {
881+ var service = db.services.add({
882+ id: 'gui',
883+ charm: 'cs:precise/juju-gui-7'
884+ });
885+ var box = views.BoundingBox(serviceModule, service);
886+ var menu = view.get('container').one('#service-menu');
887+ view.topo.set('active_service', service);
888+
889+ serviceModule.service_click_actions
890+ .toggleServiceMenu(box, serviceModule, serviceModule);
891+ menu.one('.destroy-service').hasClass('disabled').should.equal(true);
892+ });
893+
894 it('must not process service clicks after a dragend', function() {
895 // Test the work-around that prevents serviceClick from doing its work if
896 // called after dragend. Behaviour-driven testing via a tool such as
897
898=== modified file 'test/test_topology.js'
899--- test/test_topology.js 2013-01-28 19:45:48 +0000
900+++ test/test_topology.js 2013-01-31 16:56:20 +0000
901@@ -93,83 +93,4 @@
902 Y.Lang.isValue(topo.vis).should.equal(true);
903 });
904
905- it('should prevent the Juju GUI service from being destroyed', function() {
906- var service = {
907- charm: 'cs:precise/juju-gui-7'
908- };
909- var fauxTopo = {
910- get: function() {
911- return null;
912- },
913- serviceForBox: function() {
914- return service;
915- }
916- };
917- // The context is used to do the destroying, so if it does not have the
918- // destroy method, an exception will be raised if the service would be
919- // destroyed.
920- var context = {
921- get: function(name) {
922- if (name === 'component') {
923- return fauxTopo;
924- }
925- },
926- service_click_actions: {
927- hideServiceMenu: function() {},
928- destroyServiceConfirm: function() {}
929- }
930- };
931- topo.modules.ServiceModule.destroyServiceClick(undefined, context);
932- });
933-});
934-
935-describe('service menu', function() {
936- var Y, views;
937-
938- before(function(done) {
939- Y = YUI(GlobalConfig).use(['juju-topology'], function(Y) {
940- views = Y.namespace('juju.views');
941- done();
942- });
943- });
944-
945- it('should disable the "Destroy" menu for the Juju GUI service', function() {
946- var service = {
947- charm: 'cs:precise/juju-gui-7'
948- };
949- var addedClassName;
950- var menu = {
951- hasClass: function() {
952- return false;
953- },
954- addClass: function() {},
955- one: function() {
956- return {
957- addClass: function(className) {
958- addedClassName = className;
959- }
960- };
961- }
962- };
963- var fauxView = {
964- get: function(name) {
965- if (name === 'container') {
966- return {one: function() { return menu; }};
967- } else if (name === 'component') {
968- return {
969- set: function() {},
970- serviceForBox: function() {
971- return service;
972- }
973- };
974- }
975- },
976- updateServiceMenuLocation: function() {}
977- };
978- var view = new views.ServiceModule();
979- view.service_click_actions.toggleServiceMenu(
980- service, fauxView, undefined);
981- assert.equal(addedClassName, 'disabled');
982- });
983-
984 });
985
986=== modified file 'undocumented'
987--- undocumented 2013-01-25 17:51:29 +0000
988+++ undocumented 2013-01-31 16:56:20 +0000
989@@ -1,16 +1,16 @@
990-app/store/env.js:306 "status"
991-app/store/env.js:31 "initializer"
992-app/store/env.js:120 "_dispatch_rpc_result"
993-app/store/env.js:107 "dispatch_result"
994-app/store/env.js:112 "_dispatch_event"
995-app/store/env.js:222 "get_charm"
996-app/store/env.js:76 "on_message"
997-app/store/env.js:72 "on_close"
998-app/store/env.js:49 "destructor"
999-app/store/env.js:226 "get_service"
1000-app/store/env.js:70 "on_open"
1001-app/store/env.js:54 "connect"
1002-app/store/env.js:401 "get_endpoints"
1003+app/store/env.js:73 "on_open"
1004+app/store/env.js:229 "get_service"
1005+app/store/env.js:115 "_dispatch_event"
1006+app/store/env.js:79 "on_message"
1007+app/store/env.js:404 "get_endpoints"
1008+app/store/env.js:110 "dispatch_result"
1009+app/store/env.js:309 "status"
1010+app/store/env.js:75 "on_close"
1011+app/store/env.js:57 "connect"
1012+app/store/env.js:34 "initializer"
1013+app/store/env.js:123 "_dispatch_rpc_result"
1014+app/store/env.js:52 "destructor"
1015+app/store/env.js:225 "get_charm"
1016 app/store/charm.js:24 "find"
1017 app/store/charm.js:11 "success"
1018 app/store/charm.js:65 "_normalizeCharms"
1019@@ -25,32 +25,46 @@
1020 app/store/notifications.js:25 "message"
1021 app/store/notifications.js:129 "title"
1022 app/store/notifications.js:137 "message"
1023+app/views/utils.js:732 "scale"
1024 app/views/utils.js:166 "substitute"
1025-app/views/utils.js:569 "isFloat"
1026 app/views/utils.js:251 "hasSVGClass"
1027+app/views/utils.js:637 "get"
1028 app/views/utils.js:296 "toggleSVGClass"
1029-app/views/utils.js:646 "set"
1030-app/views/utils.js:645 "get"
1031+app/views/utils.js:784 "value"
1032+app/views/utils.js:698 "get"
1033 app/views/utils.js:135 "noop"
1034-app/views/utils.js:653 "get"
1035 app/views/utils.js:259 "addSVGClass"
1036+app/views/utils.js:577 "isFloat"
1037+app/views/utils.js:218 "renderable_charm"
1038 app/views/utils.js:138 "console"
1039-app/views/utils.js:218 "renderable_charm"
1040+app/views/utils.js:730 "value"
1041 app/views/utils.js:113 "noop"
1042 app/views/utils.js:280 "removeSVGClass"
1043+app/views/utils.js:714 "get"
1044 app/views/utils.js:371 "_addAlertMessage"
1045-app/views/utils.js:698 "translate"
1046 app/views/utils.js:228 "humanizeNumber"
1047-app/views/utils.js:654 "set"
1048 app/views/utils.js:339 "action"
1049+app/views/utils.js:618 "positionProp"
1050 app/views/utils.js:195 "bindModelView"
1051-app/views/utils.js:565 "isInt"
1052-app/views/utils.js:610 "BoundingBox"
1053-app/views/utils.js:639 "set"
1054+app/views/utils.js:667 "get"
1055+app/views/utils.js:644 "get"
1056+app/views/utils.js:622 "set"
1057+app/views/utils.js:573 "isInt"
1058+app/views/utils.js:638 "set"
1059+app/views/utils.js:822 "value"
1060+app/views/utils.js:670 "get"
1061+app/views/utils.js:673 "get"
1062+app/views/utils.js:749 "get"
1063+app/views/utils.js:733 "translate"
1064+app/views/utils.js:681 "get"
1065+app/views/utils.js:621 "get"
1066 app/views/utils.js:419 "invokeCallback"
1067-app/views/utils.js:612 "Box"
1068+app/views/utils.js:661 "get"
1069+app/views/utils.js:653 "set"
1070+app/views/utils.js:664 "get"
1071+app/views/utils.js:649 "get"
1072 app/views/utils.js:132 "native"
1073-app/views/utils.js:697 "scale"
1074+app/views/utils.js:795 "value"
1075 app/views/environment.js:24 "initializer"
1076 app/views/charm-panel.js:1168 "calculatePanelPosition"
1077 app/views/charm-panel.js:476 "initializer"
1078@@ -103,35 +117,35 @@
1079 app/views/unit.js:292 "retryRelation"
1080 app/views/unit.js:157 "confirmRemoved"
1081 app/views/unit.js:185 "_removeUnitCallback"
1082-app/views/service.js:875 "render"
1083-app/views/service.js:338 "fitToWindow"
1084-app/views/service.js:688 "render"
1085-app/views/service.js:288 "initializer"
1086-app/views/service.js:740 "saveConfig"
1087-app/views/service.js:191 "_destroyCallback"
1088+app/views/service.js:263 "_exposeServiceCallback"
1089+app/views/service.js:501 "updateConstraints"
1090+app/views/service.js:338 "getHeight"
1091+app/views/service.js:229 "unexposeService"
1092 app/views/service.js:116 "_removeUnitCallback"
1093-app/views/service.js:264 "_exposeServiceCallback"
1094-app/views/service.js:257 "exposeService"
1095-app/views/service.js:770 "_setConfigCallback"
1096+app/views/service.js:405 "render"
1097+app/views/service.js:699 "showErrors"
1098+app/views/service.js:769 "_setConfigCallback"
1099+app/views/service.js:874 "render"
1100+app/views/service.js:417 "confirmRemoved"
1101 app/views/service.js:52 "_modifyUnits"
1102-app/views/service.js:406 "render"
1103-app/views/service.js:418 "confirmRemoved"
1104-app/views/service.js:339 "getHeight"
1105-app/views/service.js:182 "destroyService"
1106-app/views/service.js:440 "doRemoveRelation"
1107-app/views/service.js:887 "filterUnits"
1108-app/views/service.js:615 "render"
1109+app/views/service.js:256 "exposeService"
1110+app/views/service.js:181 "destroyService"
1111+app/views/service.js:614 "render"
1112+app/views/service.js:527 "_setConstraintsCallback"
1113+app/views/service.js:287 "initializer"
1114+app/views/service.js:687 "render"
1115 app/views/service.js:30 "modifyUnits"
1116-app/views/service.js:300 "getServiceTabs"
1117-app/views/service.js:166 "confirmDestroy"
1118-app/views/service.js:502 "updateConstraints"
1119-app/views/service.js:528 "_setConstraintsCallback"
1120+app/views/service.js:739 "saveConfig"
1121+app/views/service.js:886 "filterUnits"
1122+app/views/service.js:190 "_destroyCallback"
1123+app/views/service.js:466 "_removeRelationCallback"
1124+app/views/service.js:236 "_unexposeServiceCallback"
1125+app/views/service.js:337 "fitToWindow"
1126 app/views/service.js:88 "_addUnitCallback"
1127 app/views/service.js:23 "resetUnits"
1128-app/views/service.js:700 "showErrors"
1129-app/views/service.js:230 "unexposeService"
1130-app/views/service.js:237 "_unexposeServiceCallback"
1131-app/views/service.js:467 "_removeRelationCallback"
1132+app/views/service.js:165 "confirmDestroy"
1133+app/views/service.js:299 "getServiceTabs"
1134+app/views/service.js:439 "doRemoveRelation"
1135 app/views/topology/panzoom.js:151 "rescale"
1136 app/views/topology/panzoom.js:77 "zoomHandler"
1137 app/views/topology/panzoom.js:47 "renderSlider"
1138@@ -140,55 +154,55 @@
1139 app/views/topology/panzoom.js:123 "_fire_zoom"
1140 app/views/topology/panzoom.js:105 "zoom_out"
1141 app/views/topology/panzoom.js:33 "componentBound"
1142-app/views/topology/relation.js:269 "addRelButtonClicked"
1143-app/views/topology/relation.js:362 "snapOutOfService"
1144-app/views/topology/relation.js:514 "cancelRelationBuild"
1145-app/views/topology/relation.js:602 "ambiguousAddRelationCheck"
1146+app/views/topology/relation.js:836 "relationClick"
1147+app/views/topology/relation.js:517 "cancelRelationBuild"
1148+app/views/topology/relation.js:429 "addRelationDragEnd"
1149 app/views/topology/relation.js:55 "initializer"
1150-app/views/topology/relation.js:247 "updateSubordinateRelationsCount"
1151-app/views/topology/relation.js:262 "draglineClicked"
1152-app/views/topology/relation.js:169 "drawRelationGroup"
1153-app/views/topology/relation.js:123 "updateLinks"
1154+app/views/topology/relation.js:593 "addRelationStart"
1155+app/views/topology/relation.js:367 "snapOutOfService"
1156 app/views/topology/relation.js:65 "update"
1157-app/views/topology/relation.js:378 "addRelationDragStart"
1158-app/views/topology/relation.js:85 "renderedHandler"
1159-app/views/topology/relation.js:290 "addRelation"
1160+app/views/topology/relation.js:274 "addRelButtonClicked"
1161+app/views/topology/relation.js:237 "drawRelation"
1162+app/views/topology/relation.js:84 "renderedHandler"
1163+app/views/topology/relation.js:460 "_removeRelationCallback"
1164+app/views/topology/relation.js:295 "addRelation"
1165+app/views/topology/relation.js:174 "drawRelationGroup"
1166+app/views/topology/relation.js:60 "render"
1167+app/views/topology/relation.js:689 "addRelationEnd"
1168+app/views/topology/relation.js:267 "draglineClicked"
1169+app/views/topology/relation.js:485 "removeRelationConfirm"
1170+app/views/topology/relation.js:409 "addRelationDrag"
1171+app/views/topology/relation.js:88 "processRelation"
1172+app/views/topology/relation.js:772 "subordinateRelationsForService"
1173+app/views/topology/relation.js:728 "_addRelationCallback"
1174+app/views/topology/relation.js:605 "ambiguousAddRelationCheck"
1175+app/views/topology/relation.js:252 "updateSubordinateRelationsCount"
1176+app/views/topology/relation.js:338 "snapToService"
1177+app/views/topology/relation.js:779 "subRelBlockMouseEnter"
1178+app/views/topology/relation.js:383 "addRelationDragStart"
1179+app/views/topology/relation.js:128 "updateLinks"
1180 app/views/topology/relation.js:448 "removeRelation"
1181-app/views/topology/relation.js:778 "subRelBlockMouseEnter"
1182-app/views/topology/relation.js:835 "relationClick"
1183-app/views/topology/relation.js:60 "render"
1184-app/views/topology/relation.js:403 "addRelationDrag"
1185-app/views/topology/relation.js:727 "_addRelationCallback"
1186-app/views/topology/relation.js:590 "addRelationStart"
1187-app/views/topology/relation.js:89 "processRelation"
1188-app/views/topology/relation.js:232 "drawRelation"
1189-app/views/topology/relation.js:771 "subordinateRelationsForService"
1190-app/views/topology/relation.js:457 "_removeRelationCallback"
1191-app/views/topology/relation.js:333 "snapToService"
1192-app/views/topology/relation.js:423 "addRelationDragEnd"
1193-app/views/topology/relation.js:686 "addRelationEnd"
1194-app/views/topology/relation.js:787 "subRelBlockMouseLeave"
1195-app/views/topology/relation.js:482 "removeRelationConfirm"
1196-app/views/topology/service.js:966 "destroyService"
1197-app/views/topology/service.js:758 "fade"
1198-app/views/topology/service.js:777 "renderedHandler"
1199-app/views/topology/service.js:934 "show_service"
1200-app/views/topology/service.js:136 "serviceMouseLeave"
1201-app/views/topology/service.js:108 "serviceDblClick"
1202-app/views/topology/service.js:746 "show"
1203-app/views/topology/service.js:752 "hide"
1204-app/views/topology/service.js:214 "serviceAddRelMouseDown"
1205-app/views/topology/service.js:380 "update"
1206-app/views/topology/service.js:242 "updateData"
1207-app/views/topology/service.js:975 "_destroyCallback"
1208-app/views/topology/service.js:74 "initializer"
1209-app/views/topology/service.js:84 "serviceClick"
1210-app/views/topology/service.js:825 "updateServiceMenuLocation"
1211-app/views/topology/service.js:233 "serviceAddRelMouseUp"
1212-app/views/topology/service.js:172 "serviceStatusMouseOut"
1213-app/views/topology/service.js:288 "dragend"
1214-app/views/topology/service.js:118 "serviceMouseEnter"
1215-app/views/topology/service.js:943 "destroyServiceConfirm"
1216+app/views/topology/relation.js:788 "subRelBlockMouseLeave"
1217+app/views/topology/service.js:893 "show_service"
1218+app/views/topology/service.js:112 "serviceMouseEnter"
1219+app/views/topology/service.js:731 "hide"
1220+app/views/topology/service.js:208 "serviceAddRelMouseDown"
1221+app/views/topology/service.js:130 "serviceMouseLeave"
1222+app/views/topology/service.js:737 "fade"
1223+app/views/topology/service.js:166 "serviceStatusMouseOut"
1224+app/views/topology/service.js:78 "serviceClick"
1225+app/views/topology/service.js:68 "initializer"
1226+app/views/topology/service.js:267 "dragend"
1227+app/views/topology/service.js:227 "serviceAddRelMouseUp"
1228+app/views/topology/service.js:784 "updateServiceMenuLocation"
1229+app/views/topology/service.js:725 "show"
1230+app/views/topology/service.js:102 "serviceDblClick"
1231+app/views/topology/service.js:924 "destroyService"
1232+app/views/topology/service.js:359 "update"
1233+app/views/topology/service.js:902 "destroyServiceConfirm"
1234+app/views/topology/service.js:236 "updateData"
1235+app/views/topology/service.js:932 "_destroyCallback"
1236+app/views/topology/service.js:756 "renderedHandler"
1237 app/views/topology/topology.js:163 "getter"
1238 app/views/topology/topology.js:172 "setter"
1239 app/views/topology/topology.js:159 "getter"
1240@@ -199,7 +213,6 @@
1241 app/views/topology/topology.js:177 "setter"
1242 app/views/topology/topology.js:26 "initializer"
1243 app/views/topology/topology.js:63 "renderOnce"
1244-app/views/topology/viewport.js:31 "resized"
1245 app/models/models.js:357 "getNotificationsForModel"
1246 app/models/models.js:429 "getModelListByModelName"
1247 app/models/models.js:420 "getModelById"
1248@@ -224,11 +237,11 @@
1249 app/models/models.js:239 "has_relation_for_endpoint"
1250 app/models/models.js:324 "add"
1251 app/models/models.js:338 "trim"
1252-app/models/endpoints.js:40 "add"
1253-app/models/endpoints.js:29 "convert"
1254-app/models/charm.js:155 "validator"
1255-app/models/charm.js:113 "parse"
1256-app/models/charm.js:77 "sync"
1257-app/models/charm.js:105 "failure"
1258-app/models/charm.js:48 "initializer"
1259-app/models/charm.js:129 "compare"
1260+app/models/endpoints.js:42 "add"
1261+app/models/endpoints.js:30 "convert"
1262+app/models/charm.js:157 "validator"
1263+app/models/charm.js:115 "parse"
1264+app/models/charm.js:79 "sync"
1265+app/models/charm.js:131 "compare"
1266+app/models/charm.js:107 "failure"
1267+app/models/charm.js:50 "initializer"

Subscribers

People subscribed via source and target branches