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
=== modified file 'app/views/service.js'
--- app/views/service.js 2013-01-24 19:11:19 +0000
+++ app/views/service.js 2013-01-31 16:56:20 +0000
@@ -118,7 +118,6 @@
118 getModelURL = this.get('getModelURL'),118 getModelURL = this.get('getModelURL'),
119 db = this.get('db'),119 db = this.get('db'),
120 unit_names = ev.unit_names;120 unit_names = ev.unit_names;
121 console.log('_removeUnitCallback with: ', arguments);
122121
123 if (ev.err) {122 if (ev.err) {
124 db.notifications.add(123 db.notifications.add(
125124
=== modified file 'app/views/topology/relation.js'
--- app/views/topology/relation.js 2013-01-30 12:38:03 +0000
+++ app/views/topology/relation.js 2013-01-31 16:56:20 +0000
@@ -776,8 +776,8 @@
776 */776 */
777 subordinateRelationsForService: function(service) {777 subordinateRelationsForService: function(service) {
778 return this.relations.filter(function(relation) {778 return this.relations.filter(function(relation) {
779 return (relation.source.modelId() === service.modelId() ||779 return (relation.source.modelId === service.modelId ||
780 relation.target.modelId() === service.modelId()) &&780 relation.target.modelId === service.modelId) &&
781 relation.isSubordinate;781 relation.isSubordinate;
782 });782 });
783 },783 },
784784
=== modified file 'app/views/topology/service.js'
--- app/views/topology/service.js 2013-01-30 12:38:03 +0000
+++ app/views/topology/service.js 2013-01-31 16:56:20 +0000
@@ -102,7 +102,7 @@
102 serviceDblClick: function(d, self) {102 serviceDblClick: function(d, self) {
103 // Just show the service on double-click.103 // Just show the service on double-click.
104 var topo = self.get('component'),104 var topo = self.get('component'),
105 service = topo.serviceForBox(d);105 service = d.model;
106 // The browser sends a click event right before the dblclick one, and it106 // The browser sends a click event right before the dblclick one, and it
107 // opens the service menu: close it before moving to the service details.107 // opens the service menu: close it before moving to the service details.
108 self.service_click_actions.hideServiceMenu(null, self);108 self.service_click_actions.hideServiceMenu(null, self);
@@ -194,7 +194,7 @@
194 // Get the service element194 // Get the service element
195 var topo = context.get('component');195 var topo = context.get('component');
196 var box = topo.get('active_service');196 var box = topo.get('active_service');
197 var service = topo.serviceForBox(box);197 var service = box.model;
198 context.service_click_actions198 context.service_click_actions
199 .hideServiceMenu(d, context);199 .hideServiceMenu(d, context);
200 context.service_click_actions200 context.service_click_actions
@@ -210,7 +210,7 @@
210 // Get the service element210 // Get the service element
211 var topo = context.get('component');211 var topo = context.get('component');
212 var box = topo.get('active_service');212 var box = topo.get('active_service');
213 var service = topo.serviceForBox(box);213 var service = box;
214 context.service_click_actions214 context.service_click_actions
215 .hideServiceMenu(box, context);215 .hideServiceMenu(box, context);
216 context.service_click_actions216 context.service_click_actions
@@ -252,31 +252,16 @@
252 var topo = this.get('component');252 var topo = this.get('component');
253 var vis = topo.vis;253 var vis = topo.vis;
254 var db = topo.get('db');254 var db = topo.get('db');
255 var services = db.services.map(views.toBoundingBox);255
256256 views.toBoundingBoxes(this, db.services, this.service_boxes);
257 this.services = services;257 this.services = Y.Object.values(this.service_boxes);
258
259 Y.each(services, function(service) {
260 // Update services with existing positions.
261 // In the future it would be better to sync
262 // the model to the existing box.
263 var existing = this.service_boxes[service.id];
264 if (existing) {
265 service.pos = existing.pos;
266 service.inDrag = existing.inDrag;
267 }
268 service.margins(service.subordinate ?
269 this.subordinate_margins :
270 this.service_margins);
271 this.service_boxes[service.id] = service;
272 }, this);
273258
274 // XXX: containment breaking alias, do we need this?259 // XXX: containment breaking alias, do we need this?
275 topo.service_boxes = this.service_boxes;260 topo.service_boxes = this.service_boxes;
276261
277 // Nodes are mapped by modelId tuples.262 // Nodes are mapped by modelId tuples.
278 this.node = vis.selectAll('.service')263 this.node = vis.selectAll('.service')
279 .data(services, function(d) {return d.modelId();});264 .data(this.services, function(d) {return d.modelId;});
280 },265 },
281266
282 /**267 /**
@@ -363,7 +348,7 @@
363 }348 }
364349
365 selection.attr('transform', function(d, i) {350 selection.attr('transform', function(d, i) {
366 return d.translateStr();351 return d.translateStr;
367 });352 });
368 if (topo.get('active_service') === d) {353 if (topo.get('active_service') === d) {
369 self.updateServiceMenuLocation();354 self.updateServiceMenuLocation();
@@ -443,7 +428,7 @@
443 })428 })
444 .call(this.dragBehavior)429 .call(this.dragBehavior)
445 .attr('transform', function(d) {430 .attr('transform', function(d) {
446 return d.translateStr();431 return d.translateStr;
447 })432 })
448 .call(self.createServiceNode);433 .call(self.createServiceNode);
449434
@@ -529,7 +514,7 @@
529 // binding as the event handler will514 // binding as the event handler will
530 // use that index.515 // use that index.
531 node.each(function(d) {516 node.each(function(d) {
532 var service = topo.serviceForBox(d),517 var service = d.model,
533 annotations = service.get('annotations'),518 annotations = service.get('annotations'),
534 x, y;519 x, y;
535520
@@ -667,7 +652,7 @@
667 return d.w / 10 * 7;652 return d.w / 10 * 7;
668 })653 })
669 .attr('y', function(d) {654 .attr('y', function(d) {
670 return d.getRelativeCenter()[1] - (d.w / 6) / 2;655 return d.relativeCenter[1] - (d.w / 6) / 2;
671 })656 })
672 .append('title')657 .append('title')
673 .text(function(d) {658 .text(function(d) {
@@ -712,7 +697,7 @@
712697
713 node.select('.service-status')698 node.select('.service-status')
714 .attr('transform', function(d) {699 .attr('transform', function(d) {
715 return 'translate(' + d.getRelativeCenter() + ')';700 return 'translate(' + d.relativeCenter + ')';
716 });701 });
717 node.select('.service-health-mask')702 node.select('.service-health-mask')
718 .attr('width', function(d) {703 .attr('width', function(d) {
@@ -838,7 +823,7 @@
838 var cp_width = cp.getClientRect().width,823 var cp_width = cp.getClientRect().width,
839 menu_left = service.x * z + service.w * z / 2 <824 menu_left = service.x * z + service.w * z / 2 <
840 this.width * z / 2,825 this.width * z / 2,
841 service_center = service.getRelativeCenter();826 service_center = service.relativeCenter;
842 if (menu_left) {827 if (menu_left) {
843 cp.removeClass('left')828 cp.removeClass('left')
844 .addClass('right');829 .addClass('right');
@@ -896,7 +881,7 @@
896 showServiceMenu: function(box, module, context) {881 showServiceMenu: function(box, module, context) {
897 var serviceMenu = module.get('container').one('#service-menu');882 var serviceMenu = module.get('container').one('#service-menu');
898 var topo = module.get('component');883 var topo = module.get('component');
899 var service = topo.serviceForBox(box);884 var service = box.model;
900885
901 if (box && !serviceMenu.hasClass('active')) {886 if (box && !serviceMenu.hasClass('active')) {
902 topo.set('active_service', box);887 topo.set('active_service', box);
@@ -950,12 +935,12 @@
950 */935 */
951 destroyServiceConfirm: function(m, view) {936 destroyServiceConfirm: function(m, view) {
952 // Set service in view.937 // Set service in view.
953 view.set('destroy_service', m);938 view.set('destroy_service', m.model);
954939
955 // Show dialog.940 // Show dialog.
956 view.set('destroy_dialog', views.createModalPanel(941 view.set('destroy_dialog', views.createModalPanel(
957 'Are you sure you want to destroy the service? ' +942 'Are you sure you want to destroy the service? ' +
958 'This cannot be undone.',943 'This cannot be undone.',
959 '#destroy-modal-panel',944 '#destroy-modal-panel',
960 'Destroy Service',945 'Destroy Service',
961 Y.bind(function(ev) {946 Y.bind(function(ev) {
@@ -963,9 +948,8 @@
963 var btn = ev.target;948 var btn = ev.target;
964 btn.set('disabled', true);949 btn.set('disabled', true);
965 view.service_click_actions950 view.service_click_actions
966 .destroyService(m, view, btn);951 .destroyService(m, view, btn);
967 },952 }, this)));
968 this)));
969 },953 },
970954
971 /*955 /*
@@ -976,15 +960,14 @@
976 destroyService: function(m, view, btn) {960 destroyService: function(m, view, btn) {
977 var env = view.get('component').get('env'),961 var env = view.get('component').get('env'),
978 service = view.get('destroy_service');962 service = view.get('destroy_service');
979 env.destroy_service(963 env.destroy_service(service.get('id'),
980 service.get('id'),964 Y.bind(this._destroyCallback, view,
981 Y.bind(this._destroyCallback, view,965 service, view, btn));
982 service, view, btn));
983 },966 },
984967
985 _destroyCallback: function(service, view, btn, ev) {968 _destroyCallback: function(service, view, btn, ev) {
986 var getModelURL = view.get('component').get('getModelURL'),969 var getModelURL = view.get('component').get('getModelURL'),
987 db = view.get('component').get('db');970 db = view.get('component').get('db');
988 if (ev.err) {971 if (ev.err) {
989 db.notifications.add(972 db.notifications.add(
990 new models.Notification({973 new models.Notification({
@@ -993,8 +976,7 @@
993 level: 'error',976 level: 'error',
994 link: getModelURL(service),977 link: getModelURL(service),
995 modelId: service978 modelId: service
996 })979 }));
997 );
998 } else {980 } else {
999 var relations = db.relations.get_relations_for_service(service);981 var relations = db.relations.get_relations_for_service(service);
1000 Y.each(relations, function(relation) {982 Y.each(relations, function(relation) {
@@ -1007,7 +989,6 @@
1007 btn.set('disabled', false);989 btn.set('disabled', false);
1008 }990 }
1009991
1010
1011 }992 }
1012 }, {993 }, {
1013 ATTRS: {}994 ATTRS: {}
1014995
=== modified file 'app/views/utils.js'
--- app/views/utils.js 2013-01-24 17:43:57 +0000
+++ app/views/utils.js 2013-01-31 16:56:20 +0000
@@ -607,224 +607,277 @@
607 };607 };
608608
609609
610
611 /*610 /*
612 * Utility class that encapsulates Y.Models and keeps their positional611 * Utility class that encapsulates Y.Models and keeps their position
613 * state within an svg canvas.612 * state within an svg canvas.
614 *613 *
615 * As a convenience attributes of the encapsulated model are exposed614 * As a convenience attributes of the encapsulated model are exposed
616 * directly as attributes.615 * directly as attributes.
617 */616 */
618 function BoundingBox() {617 var _box = {};
619 var x, y, w, h, value, modelId, boxMargins;618 function positionProp(name) {
620 function Box() {}619 return {
621620 get: function() {return this['_' + name];},
622 Box.model = function(_) {621 set: function(value) {
623 if (!arguments.length) {622 this['p' + name] = this['_' + name];
624 return modelId;623 this['_' + name] = value;
625 }624 }
626 modelId = [_.name, _.get('id')];625 };
627626 }
628 // Copy all the attrs from model to Box627
629 Y.mix(Box, _.getAttrs());628 Object.defineProperties(_box, {
630 return Box;629 x: positionProp('x'),
631 };630 y: positionProp('y'),
632631 w: positionProp('w'),
633 Box.margins = function(_) {632 h: positionProp('h'),
634 if (!arguments.length) {633
635 return boxMargins;634 pos: {
636 }635 get: function() { return {x: this.x, y: this.y, w: this.w, h: this.h};},
637 boxMargins = _;636 set: function(value) {
638 return Box;637 Y.mix(this, value, true, ['x', 'y', 'w', 'h']);
639 };638 }
640639 },
641 Object.defineProperties(Box, {640
642 pos: {641 translateStr: {
643 writeable: true,642 get: function() { return 'translate(' + this.x + ',' + this.y + ')';}
644 get: function() {643 },
645 return {x: this.x, y: this.y, w: this.w, h: this.h};644
646 },645 model: {
647 set: function(value) {646 get: function() {
648 Y.mix(this, value, true, ['x', 'y', 'w', 'h']);647 if (!this._modelName) { return null;}
649 }648 return this.topology.serviceForBox(this);
650 },649 },
651 x: {650 set: function(value) {
652 writeable: true,651 if (Y.Lang.isValue(value)) {
653 get: function() { return x;},652 Y.mix(this, value.getAttrs(), true);
654 set: function(value) {653 this._modelName = value.name;
655 this.px = this.x;654 }
656 x = value;655 }
657 return this;}656 },
658 },657 modelId: {
659 y: {658 get: function() { return this._modelName + '-' + this.id;}
660 writeable: true,659 },
661 get: function() { return y;},660 node: {
662 set: function(value) {661 get: function() { return this.module.getServiceNode(this.id);}
663 this.py = this.y;662 },
664 y = value;663 topology: {
665 return this;664 get: function() { return this.module.get('component');}
666 }665 },
667 }666 xy: {
668 });667 get: function() { return [this.x, this.y];}
669668 },
670 Box.getXY = function() {return [this.x, this.y];};669 wh: {
671 Box.getWH = function() {return [this.w, this.h];};670 get: function() { return [this.w, this.h];}
671 },
672
673 /*
674 * Extract margins from the supplied module.
675 */
676 margins: {
677 get: function() {
678 if (!this.module) {
679 // Used in testing.
680 return {top: 0, bottom: 0, left: 0, right: 0};
681 }
682 if (this.subordinate) {
683 return this.module.subordinate_margins;
684 }
685 return this.module.service_margins;
686 }
687 },
672688
673 /*689 /*
674 * Returns the center of the box with the origin being the upper-left690 * Returns the center of the box with the origin being the upper-left
675 * corner of the box.691 * corner of the box.
676 */692 */
677 Box.getRelativeCenter = function() {693 relativeCenter: {
678 var margins = this.margins();694 get: function() {
679 return [695 var margins = this.margins;
680 (this.w / 2) + (margins &&696 return [
681 (margins.left * this.w / 2 -697 (this.w / 2) + (margins &&
682 margins.right * this.w / 2) || 0),698 (margins.left * this.w / 2 -
683 (this.h / 2) - (margins &&699 margins.right * this.w / 2) || 0),
684 (margins.bottom * this.h / 2 -700 (this.h / 2) - (margins &&
685 margins.top * this.h / 2) || 0)701 (margins.bottom * this.h / 2 -
686 ];702 margins.top * this.h / 2) || 0)
687 };703 ];}
704 },
688705
689 /*706 /*
690 * Returns the absolute center of the box on the canvas.707 * Returns the absolute center of the box on the canvas.
691 */708 */
692 Box.getCenter = function() {709 center: {
693 var center = this.getRelativeCenter();710 get: function() {
694 center[0] += this.x;711 var c = this.relativeCenter;
695 center[1] += this.y;712 c[0] += this.x;
696 return center;713 c[1] += this.y;
697 };714 return c;
698715 }
716 },
699717
700 /*718 /*
701 * Returns true if a given point in the form [x, y] is within the box.719 * Returns true if a given point in the form [x, y] is within the box.
720 * Transform could be extracted from the topology but the current
721 * arguments ease testing.
702 */722 */
703 Box.containsPoint = function(point, transform) {723 containsPoint: {
704 transform = transform || {724 writable: true, // For test overrides.
705 scale: function() { return 1; },725 configurable: true,
706 translate: function() { return [0, 0]; }726 value: function(point, transform) {
707 };727 transform = transform || {
708 var s = transform.scale(), tr = transform.translate();728 scale: function() { return 1; },
709 if (point[0] >= this.x * s + tr[0] &&729 translate: function() { return [0, 0]; }
710 point[0] <= this.x * s + this.w * s + tr[0] &&730 };
711 point[1] >= this.y * s + tr[1] &&731 var tr = transform.translate(),
712 point[1] <= this.y * s + this.h * s + tr[1]) {732 s = transform.scale();
713 return true;733
734 return (point[0] >= this.x * s + tr[0] &&
735 point[0] <= this.x * s + this.w * s + tr[0] &&
736 point[1] >= this.y * s + tr[1] &&
737 point[1] <= this.y * s + this.h * s + tr[1]);
714 }738 }
715 return false;739 },
716 };
717740
718 /*741 /*
719 * Return the 50% points along each side as xy pairs742 * Return the 50% points along each side as [x, y] pairs.
720 */743 */
721 Box.getConnectors = function() {744 connectors: {
722 // Since the service nodes have a shadow that takes up a bit of745 get: function() {
723 // space on the sides and bottom of the actual node itself, add a bit746 // Since the service nodes have a shadow that takes up a bit of
724 // of a margin to the actual connecting points. The margin is specified747 // space on the sides and bottom of the actual node itself, add a bit
725 // as a percentage of the width or height, as those are affected by the748 // of a margin to the actual connecting points. The margin is specified
726 // scale. This is calculated by taking the distance of the shadow from749 // as a percentage of the width or height, as those are affected by the
727 // the edge of the actual shape and calculating it as a percentage of750 // scale. This is calculated by taking the distance of the shadow from
728 // the total height of the shape.751 // the edge of the actual shape and calculating it as a percentage of
729 var margins = this.margins();752 // the total height of the shape.
730 return {753 var margins = this.margins;
731 top: [754 return {
732 this.x + (this.w / 2),755 top: [
733 this.y + (margins && (margins.top * this.h) || 0)756 this.x + (this.w / 2),
734 ],757 this.y + (margins && (margins.top * this.h) || 0)
735 right: [758 ],
736 this.x + this.w - (margins && (margins.right * this.w) || 0),759 right: [
737 this.y + (this.h / 2) - (760 this.x + this.w - (margins && (margins.right * this.w) || 0),
738 margins && (margins.bottom * this.h / 2 -761 this.y + (this.h / 2) - (
739 margins.top * this.h / 2) || 0)762 margins && (margins.bottom * this.h / 2 -
740 ],763 margins.top * this.h / 2) || 0)
741 bottom: [764 ],
742 this.x + (this.w / 2),765 bottom: [
743 this.y + this.h - (margins && (margins.bottom * this.h) || 0)766 this.x + (this.w / 2),
744 ],767 this.y + this.h - (margins && (margins.bottom * this.h) || 0)
745 left: [768 ],
746 this.x + (margins && (margins.left * this.w) || 0),769 left: [
747 this.y + (this.h / 2) - (770 this.x + (margins && (margins.left * this.w) || 0),
748 margins && (margins.bottom * this.h / 2 -771 this.y + (this.h / 2) - (
749 margins.top * this.h / 2) || 0)772 margins && (margins.bottom * this.h / 2 -
750 ]773 margins.top * this.h / 2) || 0)
751 };774 ]
752 };775 };
776 }
777 },
753778
754 Box._distance = function(xy1, xy2) {779 _distance: {
755 return Math.sqrt(Math.pow(xy1[0] - xy2[0], 2) +780 value: function(xy1, xy2) {
756 Math.pow(xy1[1] - xy2[1], 2));781 return Math.sqrt(Math.pow(xy1[0] - xy2[0], 2) +
757 };782 Math.pow(xy1[1] - xy2[1], 2));
783 }
784 },
758785
759 /*786 /*
760 * Connectors are defined on four borders, find the one closes to787 * Connectors are defined on four borders, find the one closes to
761 * another BoundingBox788 * another BoundingBox
762 */789 */
763 Box.getNearestConnector = function(other_box) {790 getNearestConnector: {
764 var connectors = this.getConnectors(),791 value: function(box_or_xy) {
765 result = null,792 var connectors = this.connectors,
766 shortest_d = Infinity,793 result = null,
767 source = other_box;794 shortest_d = Infinity,
768 // duck typing795 source = box_or_xy;
769 if ('getXY' in other_box) {796
770 source = other_box.getXY();797 if (box_or_xy.xy !== undefined) {
798 source = box_or_xy.xy;
799 }
800 Y.each(connectors, function(ep) {
801 // Take the distance of each XY pair
802 var d = this._distance(source, ep);
803 if (!Y.Lang.isValue(result) || d < shortest_d) {
804 shortest_d = d;
805 result = ep;
806 }
807 }, this);
808 return result;
771 }809 }
772810 },
773 Y.each(connectors, function(ep) {
774 // Take the distance of each XY pair
775 var d = this._distance(source, ep);
776 if (!Y.Lang.isValue(result) || d < shortest_d) {
777 shortest_d = d;
778 result = ep;
779 }
780 }, this);
781 return result;
782 };
783811
784 /*812 /*
785 * Return [this.connector.XY, other.connector.XY] (in that order)813 * Return [this.connector.XY, other.connector.XY] (in that order)
786 * that as nearest to each other. This can be used to define start-end814 * that as nearest to each other. This can be used to define start-end
787 * points for routing.815 * points for routing.
788 */816 */
789 Box.getConnectorPair = function(other_box) {817 getConnectorPair: {
790 var sc = Box.getConnectors(),818 value: function(other_box) {
791 oc = other_box.getConnectors(),819 var sc = this.connectors,
792 result = null,820 oc = other_box.connectors,
793 shortest_d = Infinity;821 result = null,
794822 shortest_d = Infinity;
795 Y.each(sc, function(ep1) {823
796 Y.each(oc, function(ep2) {824 Y.each(sc, function(ep1) {
797 // Take the distance of each XY pair825 Y.each(oc, function(ep2) {
798 var d = this._distance(ep1, ep2);826 // Take the distance of each XY pair
799 if (!Y.Lang.isValue(result) || d < shortest_d) {827 var d = this._distance(ep1, ep2);
800 shortest_d = d;828 if (!Y.Lang.isValue(result) || d < shortest_d) {
801 result = [ep1, ep2];829 shortest_d = d;
802 }830 result = [ep1, ep2];
803 }, other_box);831 }
804 }, this);832 }, other_box);
805 return result;833 }, this);
806 };834 return result;
807835 }
808 Box.translateStr = function() {836 }
809 return 'translate(' + this.getXY() + ')';837 });
810 };838
811839 /**
812 Box.modelId = function() {840 * @method BoundingBox
813 return modelId[0] + '-' + modelId[1];841 * @param {Module} module Typically service module.
814 };842 * @param {Model} model Model object.
815843 * @return {BoundingBox} A Box model.
816 return Box;844 */
845 function BoundingBox(module, model) {
846 var b = Object.create(_box);
847 b.module = module;
848 b.model = model;
849
850 return b;
817 }851 }
818852
819 views.BoundingBox = BoundingBox;853 views.BoundingBox = BoundingBox;
820854
821 views.toBoundingBox = function(model) {855 /**
822 var box = new BoundingBox();856 * Covert an Array of services into BoundingBoxes. If
823 box.model(model);857 * existing is supplied it should be a map of {id: box}
824 return box;858 * and will be updated in place by merging changed attribute
859 * into the index.
860 *
861 * @param {ServiceModule} Module holding box canvas and context.
862 * @param {ModelList} services Service modellist.
863 * @param {Object} existing id:box mapping.
864 * @return {Object} id:box mapping.
865 **/
866 views.toBoundingBoxes = function(module, services, existing) {
867 var result = existing || {};
868 Y.each(services, function() {
869 var id = this.get('id');
870 if (result[id] !== undefined) {
871 result[id].model = this;
872 } else {
873 result[id] = new BoundingBox(module, this);
874 }
875 });
876 return result;
825 };877 };
826878
827879
880
828 /**881 /**
829 * Decorate a relation with some related/derived data.882 * Decorate a relation with some related/derived data.
830 *883 *
@@ -841,9 +894,9 @@
841 source: source,894 source: source,
842 target: target,895 target: target,
843 compositeId: (896 compositeId: (
844 source.modelId() +897 source.modelId +
845 (hasRelations ? ':' + relation.endpoints[0][1].name : '') +898 (hasRelations ? ':' + relation.endpoints[0][1].name : '') +
846 '-' + target.modelId() +899 '-' + target.modelId +
847 (hasRelations ? ':' + relation.endpoints[1][1].name : ''))900 (hasRelations ? ':' + relation.endpoints[1][1].name : ''))
848 };901 };
849 Y.mix(decorated, relation.getAttrs());902 Y.mix(decorated, relation.getAttrs());
850903
=== modified file 'test/test_environment_view.js'
--- test/test_environment_view.js 2013-01-29 19:12:52 +0000
+++ test/test_environment_view.js 2013-01-31 16:56:20 +0000
@@ -789,7 +789,7 @@
789 });789 });
790790
791 describe('view model support infrastructure', function() {791 describe('view model support infrastructure', function() {
792 var Y, views, models;792 var Y, views, models, module, service;
793793
794 before(function(done) {794 before(function(done) {
795 Y = YUI(GlobalConfig).use(['juju-views', 'juju-models'],795 Y = YUI(GlobalConfig).use(['juju-views', 'juju-models'],
@@ -800,48 +800,46 @@
800 });800 });
801 });801 });
802802
803 beforeEach(function() {
804 service = new models.Service({
805 id: 'mediawiki',
806 exposed: true});
807 module = {
808 topology: {
809 serviceForBox: function() {return service;}
810 }};
811 });
812
803 it('must be able to get us nearest connectors',813 it('must be able to get us nearest connectors',
804 function() {814 function() {
805 var b1 = views.BoundingBox(),815 var b1 = views.BoundingBox(module, service),
806 b2 = views.BoundingBox();816 b2 = views.BoundingBox(module, service);
807817
808 // raw poperty access818 // raw property access
809 b1.x = 0; b1.y = 0;819 b1.x = 0; b1.y = 0;
810 b1.w = 100; b1.h = 200;820 b1.w = 100; b1.h = 200;
811821
812 // Use pos to set b2822 // Use pos to set b2
813 b2.pos = {x: 200, y: 300, w: 100, h: 200};823 b2.pos = {x: 200, y: 300, w: 100, h: 200};
814824
815 b1.getXY().should.eql([0, 0]);825 b1.xy.should.eql([0, 0]);
816 b2.getWH().should.eql([100, 200]);826 b2.wh.should.eql([100, 200]);
817827
818 b1.margins({
819 top: 0,
820 bottom: 0,
821 right: 0,
822 left: 0
823 });
824 b1.getNearestConnector([0, 0]);828 b1.getNearestConnector([0, 0]);
825829
826 b1.getNearestConnector(b2).should830 b1.getNearestConnector(b2).should
827 .eql(b1.getConnectors().bottom);831 .eql(b1.connectors.bottom);
828832
829 b2.margins({
830 top: 0,
831 bottom: 0,
832 right: 0,
833 left: 0
834 });
835 b2.getNearestConnector(b1).should833 b2.getNearestConnector(b1).should
836 .eql(b2.getConnectors().top);834 .eql(b2.connectors.top);
837835
838 b1.getConnectorPair(b2).should.eql([836 b1.getConnectorPair(b2).should.eql([
839 b1.getConnectors().bottom,837 b1.connectors.bottom,
840 b2.getConnectors().top]);838 b2.connectors.top]);
841 });839 });
842840
843 it('must be able to tell if a point is inside a box', function() {841 it('must be able to tell if a point is inside a box', function() {
844 var b = views.BoundingBox();842 var b = views.BoundingBox(module, service);
845 b.pos = {x: 100, y: 100, w: 50, h: 50};843 b.pos = {x: 100, y: 100, w: 50, h: 50};
846844
847 b.containsPoint([125, 125]).should.equal(true);845 b.containsPoint([125, 125]).should.equal(true);
@@ -850,10 +848,10 @@
850848
851 it('must be able to save and restore old position information',849 it('must be able to save and restore old position information',
852 function() {850 function() {
853 var b1 = views.BoundingBox(),851 var b1 = views.BoundingBox(module, service),
854 b2 = views.BoundingBox();852 b2 = views.BoundingBox(module, service);
855853
856 // raw poperty access854 // raw property access
857 b1.x = 0; b1.y = 0;855 b1.x = 0; b1.y = 0;
858 b1.w = 100; b1.h = 200;856 b1.w = 100; b1.h = 200;
859857
@@ -872,13 +870,10 @@
872870
873 });871 });
874872
875 it('must be able to access model attributes easily', function() {873 it('must be able to access model attributes', function() {
876 var service = new models.Service({id: 'mediawiki',874 var b1 = new views.BoundingBox(module, service);
877 exposed: true}),
878 b1 = new views.BoundingBox();
879 b1.model(service);
880875
881 b1.modelId().should.equal('service-mediawiki');876 b1.modelId.should.equal('service-mediawiki');
882877
883 // properties of the model have mapped to the box878 // properties of the model have mapped to the box
884 b1.id.should.equal('mediawiki');879 b1.id.should.equal('mediawiki');
@@ -887,36 +882,49 @@
887882
888 it('must be able to update position data and not touch model data',883 it('must be able to update position data and not touch model data',
889 function() {884 function() {
890 var service = new models.Service({id: 'mediawiki',885 var b1 = views.BoundingBox(module, service);
891 exposed: true}),
892 b1 = new views.BoundingBox();
893 b1.model(service);
894 b1.x = 0; b1.y = 0;886 b1.x = 0; b1.y = 0;
895 b1.w = 100; b1.h = 200;887 b1.w = 100; b1.h = 200;
896 b1.id.should.equal('mediawiki');888 b1.id.should.equal('mediawiki');
897889
898 // X/Y updated, other keys ignored890 // X/Y updated, other keys ignored
899 b1.pos = {x: 100, y: 100, id: 'mediawiki'};891 b1.pos = {x: 100, y: 100, id: 'blubber'};
900 b1.x.should.equal(100);892 b1.x.should.equal(100);
901 b1.id.should.equal('mediawiki');893 b1.id.should.equal('mediawiki');
902
903 });894 });
904895
905 it('must be able to map from sequence of models to boundingboxes',896 it('must be able to map from sequence of models to boundingboxes',
906 function() {897 function() {
907 var services = new models.ServiceList();898 var services = new models.ServiceList();
908 services.add([{id: 'mysql'},899 services.add([{id: 'mysql'},
909 {id: 'haproxy'},900 {id: 'haproxy'},
910 {id: 'memcache'},901 {id: 'memcache'},
911 {id: 'wordpress'}]);902 {id: 'wordpress'}]);
912903
913 services.size().should.equal(4);904 services.size().should.equal(4);
914 var boxes = services.map(views.toBoundingBox);905 var boxes = views.toBoundingBoxes(module, services);
915 boxes.length.should.equal(4);906 boxes.mysql.id.should.equal('mysql');
916 boxes[0].id.should.equal('mysql');907 boxes.wordpress.id.should.equal('wordpress');
917 boxes[3].id.should.equal('wordpress');908 });
918 });909
919910 it('must be able to update boxes with new model data',
911 function() {
912 var services = new models.ServiceList();
913 services.add([{id: 'mysql', exposed: false},
914 {id: 'haproxy'},
915 {id: 'memcache'},
916 {id: 'wordpress'}]);
917
918 services.size().should.equal(4);
919 var boxes = views.toBoundingBoxes(module, services);
920 var mysql = services.getById('mysql');
921
922 boxes.mysql.exposed.should.equal(false);
923 mysql.set('exposed', true);
924
925 // The third argument here implies an update.
926 views.toBoundingBoxes(module, services, boxes);
927 boxes.mysql.exposed.should.equal(true);
928 });
920 });929 });
921
922})();930})();
923931
=== modified file 'test/test_service_module.js'
--- test/test_service_module.js 2013-01-29 18:59:37 +0000
+++ test/test_service_module.js 2013-01-31 16:56:20 +0000
@@ -232,6 +232,20 @@
232 cancelButton.simulate('click');232 cancelButton.simulate('click');
233 });233 });
234234
235 it('should prevent the Juju GUI service from being destroyed', function() {
236 var service = db.services.add({
237 id: 'gui',
238 charm: 'cs:precise/juju-gui-7'
239 });
240 var box = views.BoundingBox(serviceModule, service);
241 var menu = view.get('container').one('#service-menu');
242 view.topo.set('active_service', service);
243
244 serviceModule.service_click_actions
245 .toggleServiceMenu(box, serviceModule, serviceModule);
246 menu.one('.destroy-service').hasClass('disabled').should.equal(true);
247 });
248
235 it('must not process service clicks after a dragend', function() {249 it('must not process service clicks after a dragend', function() {
236 // Test the work-around that prevents serviceClick from doing its work if250 // Test the work-around that prevents serviceClick from doing its work if
237 // called after dragend. Behaviour-driven testing via a tool such as251 // called after dragend. Behaviour-driven testing via a tool such as
238252
=== modified file 'test/test_topology.js'
--- test/test_topology.js 2013-01-28 19:45:48 +0000
+++ test/test_topology.js 2013-01-31 16:56:20 +0000
@@ -93,83 +93,4 @@
93 Y.Lang.isValue(topo.vis).should.equal(true);93 Y.Lang.isValue(topo.vis).should.equal(true);
94 });94 });
9595
96 it('should prevent the Juju GUI service from being destroyed', function() {
97 var service = {
98 charm: 'cs:precise/juju-gui-7'
99 };
100 var fauxTopo = {
101 get: function() {
102 return null;
103 },
104 serviceForBox: function() {
105 return service;
106 }
107 };
108 // The context is used to do the destroying, so if it does not have the
109 // destroy method, an exception will be raised if the service would be
110 // destroyed.
111 var context = {
112 get: function(name) {
113 if (name === 'component') {
114 return fauxTopo;
115 }
116 },
117 service_click_actions: {
118 hideServiceMenu: function() {},
119 destroyServiceConfirm: function() {}
120 }
121 };
122 topo.modules.ServiceModule.destroyServiceClick(undefined, context);
123 });
124});
125
126describe('service menu', function() {
127 var Y, views;
128
129 before(function(done) {
130 Y = YUI(GlobalConfig).use(['juju-topology'], function(Y) {
131 views = Y.namespace('juju.views');
132 done();
133 });
134 });
135
136 it('should disable the "Destroy" menu for the Juju GUI service', function() {
137 var service = {
138 charm: 'cs:precise/juju-gui-7'
139 };
140 var addedClassName;
141 var menu = {
142 hasClass: function() {
143 return false;
144 },
145 addClass: function() {},
146 one: function() {
147 return {
148 addClass: function(className) {
149 addedClassName = className;
150 }
151 };
152 }
153 };
154 var fauxView = {
155 get: function(name) {
156 if (name === 'container') {
157 return {one: function() { return menu; }};
158 } else if (name === 'component') {
159 return {
160 set: function() {},
161 serviceForBox: function() {
162 return service;
163 }
164 };
165 }
166 },
167 updateServiceMenuLocation: function() {}
168 };
169 var view = new views.ServiceModule();
170 view.service_click_actions.toggleServiceMenu(
171 service, fauxView, undefined);
172 assert.equal(addedClassName, 'disabled');
173 });
174
175});96});
17697
=== modified file 'undocumented'
--- undocumented 2013-01-25 17:51:29 +0000
+++ undocumented 2013-01-31 16:56:20 +0000
@@ -1,16 +1,16 @@
1app/store/env.js:306 "status"1app/store/env.js:73 "on_open"
2app/store/env.js:31 "initializer"2app/store/env.js:229 "get_service"
3app/store/env.js:120 "_dispatch_rpc_result"3app/store/env.js:115 "_dispatch_event"
4app/store/env.js:107 "dispatch_result"4app/store/env.js:79 "on_message"
5app/store/env.js:112 "_dispatch_event"5app/store/env.js:404 "get_endpoints"
6app/store/env.js:222 "get_charm"6app/store/env.js:110 "dispatch_result"
7app/store/env.js:76 "on_message"7app/store/env.js:309 "status"
8app/store/env.js:72 "on_close"8app/store/env.js:75 "on_close"
9app/store/env.js:49 "destructor"9app/store/env.js:57 "connect"
10app/store/env.js:226 "get_service"10app/store/env.js:34 "initializer"
11app/store/env.js:70 "on_open"11app/store/env.js:123 "_dispatch_rpc_result"
12app/store/env.js:54 "connect"12app/store/env.js:52 "destructor"
13app/store/env.js:401 "get_endpoints"13app/store/env.js:225 "get_charm"
14app/store/charm.js:24 "find"14app/store/charm.js:24 "find"
15app/store/charm.js:11 "success"15app/store/charm.js:11 "success"
16app/store/charm.js:65 "_normalizeCharms"16app/store/charm.js:65 "_normalizeCharms"
@@ -25,32 +25,46 @@
25app/store/notifications.js:25 "message"25app/store/notifications.js:25 "message"
26app/store/notifications.js:129 "title"26app/store/notifications.js:129 "title"
27app/store/notifications.js:137 "message"27app/store/notifications.js:137 "message"
28app/views/utils.js:732 "scale"
28app/views/utils.js:166 "substitute"29app/views/utils.js:166 "substitute"
29app/views/utils.js:569 "isFloat"
30app/views/utils.js:251 "hasSVGClass"30app/views/utils.js:251 "hasSVGClass"
31app/views/utils.js:637 "get"
31app/views/utils.js:296 "toggleSVGClass"32app/views/utils.js:296 "toggleSVGClass"
32app/views/utils.js:646 "set"33app/views/utils.js:784 "value"
33app/views/utils.js:645 "get"34app/views/utils.js:698 "get"
34app/views/utils.js:135 "noop"35app/views/utils.js:135 "noop"
35app/views/utils.js:653 "get"
36app/views/utils.js:259 "addSVGClass"36app/views/utils.js:259 "addSVGClass"
37app/views/utils.js:577 "isFloat"
38app/views/utils.js:218 "renderable_charm"
37app/views/utils.js:138 "console"39app/views/utils.js:138 "console"
38app/views/utils.js:218 "renderable_charm"40app/views/utils.js:730 "value"
39app/views/utils.js:113 "noop"41app/views/utils.js:113 "noop"
40app/views/utils.js:280 "removeSVGClass"42app/views/utils.js:280 "removeSVGClass"
43app/views/utils.js:714 "get"
41app/views/utils.js:371 "_addAlertMessage"44app/views/utils.js:371 "_addAlertMessage"
42app/views/utils.js:698 "translate"
43app/views/utils.js:228 "humanizeNumber"45app/views/utils.js:228 "humanizeNumber"
44app/views/utils.js:654 "set"
45app/views/utils.js:339 "action"46app/views/utils.js:339 "action"
47app/views/utils.js:618 "positionProp"
46app/views/utils.js:195 "bindModelView"48app/views/utils.js:195 "bindModelView"
47app/views/utils.js:565 "isInt"49app/views/utils.js:667 "get"
48app/views/utils.js:610 "BoundingBox"50app/views/utils.js:644 "get"
49app/views/utils.js:639 "set"51app/views/utils.js:622 "set"
52app/views/utils.js:573 "isInt"
53app/views/utils.js:638 "set"
54app/views/utils.js:822 "value"
55app/views/utils.js:670 "get"
56app/views/utils.js:673 "get"
57app/views/utils.js:749 "get"
58app/views/utils.js:733 "translate"
59app/views/utils.js:681 "get"
60app/views/utils.js:621 "get"
50app/views/utils.js:419 "invokeCallback"61app/views/utils.js:419 "invokeCallback"
51app/views/utils.js:612 "Box"62app/views/utils.js:661 "get"
63app/views/utils.js:653 "set"
64app/views/utils.js:664 "get"
65app/views/utils.js:649 "get"
52app/views/utils.js:132 "native"66app/views/utils.js:132 "native"
53app/views/utils.js:697 "scale"67app/views/utils.js:795 "value"
54app/views/environment.js:24 "initializer"68app/views/environment.js:24 "initializer"
55app/views/charm-panel.js:1168 "calculatePanelPosition"69app/views/charm-panel.js:1168 "calculatePanelPosition"
56app/views/charm-panel.js:476 "initializer"70app/views/charm-panel.js:476 "initializer"
@@ -103,35 +117,35 @@
103app/views/unit.js:292 "retryRelation"117app/views/unit.js:292 "retryRelation"
104app/views/unit.js:157 "confirmRemoved"118app/views/unit.js:157 "confirmRemoved"
105app/views/unit.js:185 "_removeUnitCallback"119app/views/unit.js:185 "_removeUnitCallback"
106app/views/service.js:875 "render"120app/views/service.js:263 "_exposeServiceCallback"
107app/views/service.js:338 "fitToWindow"121app/views/service.js:501 "updateConstraints"
108app/views/service.js:688 "render"122app/views/service.js:338 "getHeight"
109app/views/service.js:288 "initializer"123app/views/service.js:229 "unexposeService"
110app/views/service.js:740 "saveConfig"
111app/views/service.js:191 "_destroyCallback"
112app/views/service.js:116 "_removeUnitCallback"124app/views/service.js:116 "_removeUnitCallback"
113app/views/service.js:264 "_exposeServiceCallback"125app/views/service.js:405 "render"
114app/views/service.js:257 "exposeService"126app/views/service.js:699 "showErrors"
115app/views/service.js:770 "_setConfigCallback"127app/views/service.js:769 "_setConfigCallback"
128app/views/service.js:874 "render"
129app/views/service.js:417 "confirmRemoved"
116app/views/service.js:52 "_modifyUnits"130app/views/service.js:52 "_modifyUnits"
117app/views/service.js:406 "render"131app/views/service.js:256 "exposeService"
118app/views/service.js:418 "confirmRemoved"132app/views/service.js:181 "destroyService"
119app/views/service.js:339 "getHeight"133app/views/service.js:614 "render"
120app/views/service.js:182 "destroyService"134app/views/service.js:527 "_setConstraintsCallback"
121app/views/service.js:440 "doRemoveRelation"135app/views/service.js:287 "initializer"
122app/views/service.js:887 "filterUnits"136app/views/service.js:687 "render"
123app/views/service.js:615 "render"
124app/views/service.js:30 "modifyUnits"137app/views/service.js:30 "modifyUnits"
125app/views/service.js:300 "getServiceTabs"138app/views/service.js:739 "saveConfig"
126app/views/service.js:166 "confirmDestroy"139app/views/service.js:886 "filterUnits"
127app/views/service.js:502 "updateConstraints"140app/views/service.js:190 "_destroyCallback"
128app/views/service.js:528 "_setConstraintsCallback"141app/views/service.js:466 "_removeRelationCallback"
142app/views/service.js:236 "_unexposeServiceCallback"
143app/views/service.js:337 "fitToWindow"
129app/views/service.js:88 "_addUnitCallback"144app/views/service.js:88 "_addUnitCallback"
130app/views/service.js:23 "resetUnits"145app/views/service.js:23 "resetUnits"
131app/views/service.js:700 "showErrors"146app/views/service.js:165 "confirmDestroy"
132app/views/service.js:230 "unexposeService"147app/views/service.js:299 "getServiceTabs"
133app/views/service.js:237 "_unexposeServiceCallback"148app/views/service.js:439 "doRemoveRelation"
134app/views/service.js:467 "_removeRelationCallback"
135app/views/topology/panzoom.js:151 "rescale"149app/views/topology/panzoom.js:151 "rescale"
136app/views/topology/panzoom.js:77 "zoomHandler"150app/views/topology/panzoom.js:77 "zoomHandler"
137app/views/topology/panzoom.js:47 "renderSlider"151app/views/topology/panzoom.js:47 "renderSlider"
@@ -140,55 +154,55 @@
140app/views/topology/panzoom.js:123 "_fire_zoom"154app/views/topology/panzoom.js:123 "_fire_zoom"
141app/views/topology/panzoom.js:105 "zoom_out"155app/views/topology/panzoom.js:105 "zoom_out"
142app/views/topology/panzoom.js:33 "componentBound"156app/views/topology/panzoom.js:33 "componentBound"
143app/views/topology/relation.js:269 "addRelButtonClicked"157app/views/topology/relation.js:836 "relationClick"
144app/views/topology/relation.js:362 "snapOutOfService"158app/views/topology/relation.js:517 "cancelRelationBuild"
145app/views/topology/relation.js:514 "cancelRelationBuild"159app/views/topology/relation.js:429 "addRelationDragEnd"
146app/views/topology/relation.js:602 "ambiguousAddRelationCheck"
147app/views/topology/relation.js:55 "initializer"160app/views/topology/relation.js:55 "initializer"
148app/views/topology/relation.js:247 "updateSubordinateRelationsCount"161app/views/topology/relation.js:593 "addRelationStart"
149app/views/topology/relation.js:262 "draglineClicked"162app/views/topology/relation.js:367 "snapOutOfService"
150app/views/topology/relation.js:169 "drawRelationGroup"
151app/views/topology/relation.js:123 "updateLinks"
152app/views/topology/relation.js:65 "update"163app/views/topology/relation.js:65 "update"
153app/views/topology/relation.js:378 "addRelationDragStart"164app/views/topology/relation.js:274 "addRelButtonClicked"
154app/views/topology/relation.js:85 "renderedHandler"165app/views/topology/relation.js:237 "drawRelation"
155app/views/topology/relation.js:290 "addRelation"166app/views/topology/relation.js:84 "renderedHandler"
167app/views/topology/relation.js:460 "_removeRelationCallback"
168app/views/topology/relation.js:295 "addRelation"
169app/views/topology/relation.js:174 "drawRelationGroup"
170app/views/topology/relation.js:60 "render"
171app/views/topology/relation.js:689 "addRelationEnd"
172app/views/topology/relation.js:267 "draglineClicked"
173app/views/topology/relation.js:485 "removeRelationConfirm"
174app/views/topology/relation.js:409 "addRelationDrag"
175app/views/topology/relation.js:88 "processRelation"
176app/views/topology/relation.js:772 "subordinateRelationsForService"
177app/views/topology/relation.js:728 "_addRelationCallback"
178app/views/topology/relation.js:605 "ambiguousAddRelationCheck"
179app/views/topology/relation.js:252 "updateSubordinateRelationsCount"
180app/views/topology/relation.js:338 "snapToService"
181app/views/topology/relation.js:779 "subRelBlockMouseEnter"
182app/views/topology/relation.js:383 "addRelationDragStart"
183app/views/topology/relation.js:128 "updateLinks"
156app/views/topology/relation.js:448 "removeRelation"184app/views/topology/relation.js:448 "removeRelation"
157app/views/topology/relation.js:778 "subRelBlockMouseEnter"185app/views/topology/relation.js:788 "subRelBlockMouseLeave"
158app/views/topology/relation.js:835 "relationClick"186app/views/topology/service.js:893 "show_service"
159app/views/topology/relation.js:60 "render"187app/views/topology/service.js:112 "serviceMouseEnter"
160app/views/topology/relation.js:403 "addRelationDrag"188app/views/topology/service.js:731 "hide"
161app/views/topology/relation.js:727 "_addRelationCallback"189app/views/topology/service.js:208 "serviceAddRelMouseDown"
162app/views/topology/relation.js:590 "addRelationStart"190app/views/topology/service.js:130 "serviceMouseLeave"
163app/views/topology/relation.js:89 "processRelation"191app/views/topology/service.js:737 "fade"
164app/views/topology/relation.js:232 "drawRelation"192app/views/topology/service.js:166 "serviceStatusMouseOut"
165app/views/topology/relation.js:771 "subordinateRelationsForService"193app/views/topology/service.js:78 "serviceClick"
166app/views/topology/relation.js:457 "_removeRelationCallback"194app/views/topology/service.js:68 "initializer"
167app/views/topology/relation.js:333 "snapToService"195app/views/topology/service.js:267 "dragend"
168app/views/topology/relation.js:423 "addRelationDragEnd"196app/views/topology/service.js:227 "serviceAddRelMouseUp"
169app/views/topology/relation.js:686 "addRelationEnd"197app/views/topology/service.js:784 "updateServiceMenuLocation"
170app/views/topology/relation.js:787 "subRelBlockMouseLeave"198app/views/topology/service.js:725 "show"
171app/views/topology/relation.js:482 "removeRelationConfirm"199app/views/topology/service.js:102 "serviceDblClick"
172app/views/topology/service.js:966 "destroyService"200app/views/topology/service.js:924 "destroyService"
173app/views/topology/service.js:758 "fade"201app/views/topology/service.js:359 "update"
174app/views/topology/service.js:777 "renderedHandler"202app/views/topology/service.js:902 "destroyServiceConfirm"
175app/views/topology/service.js:934 "show_service"203app/views/topology/service.js:236 "updateData"
176app/views/topology/service.js:136 "serviceMouseLeave"204app/views/topology/service.js:932 "_destroyCallback"
177app/views/topology/service.js:108 "serviceDblClick"205app/views/topology/service.js:756 "renderedHandler"
178app/views/topology/service.js:746 "show"
179app/views/topology/service.js:752 "hide"
180app/views/topology/service.js:214 "serviceAddRelMouseDown"
181app/views/topology/service.js:380 "update"
182app/views/topology/service.js:242 "updateData"
183app/views/topology/service.js:975 "_destroyCallback"
184app/views/topology/service.js:74 "initializer"
185app/views/topology/service.js:84 "serviceClick"
186app/views/topology/service.js:825 "updateServiceMenuLocation"
187app/views/topology/service.js:233 "serviceAddRelMouseUp"
188app/views/topology/service.js:172 "serviceStatusMouseOut"
189app/views/topology/service.js:288 "dragend"
190app/views/topology/service.js:118 "serviceMouseEnter"
191app/views/topology/service.js:943 "destroyServiceConfirm"
192app/views/topology/topology.js:163 "getter"206app/views/topology/topology.js:163 "getter"
193app/views/topology/topology.js:172 "setter"207app/views/topology/topology.js:172 "setter"
194app/views/topology/topology.js:159 "getter"208app/views/topology/topology.js:159 "getter"
@@ -199,7 +213,6 @@
199app/views/topology/topology.js:177 "setter"213app/views/topology/topology.js:177 "setter"
200app/views/topology/topology.js:26 "initializer"214app/views/topology/topology.js:26 "initializer"
201app/views/topology/topology.js:63 "renderOnce"215app/views/topology/topology.js:63 "renderOnce"
202app/views/topology/viewport.js:31 "resized"
203app/models/models.js:357 "getNotificationsForModel"216app/models/models.js:357 "getNotificationsForModel"
204app/models/models.js:429 "getModelListByModelName"217app/models/models.js:429 "getModelListByModelName"
205app/models/models.js:420 "getModelById"218app/models/models.js:420 "getModelById"
@@ -224,11 +237,11 @@
224app/models/models.js:239 "has_relation_for_endpoint"237app/models/models.js:239 "has_relation_for_endpoint"
225app/models/models.js:324 "add"238app/models/models.js:324 "add"
226app/models/models.js:338 "trim"239app/models/models.js:338 "trim"
227app/models/endpoints.js:40 "add"240app/models/endpoints.js:42 "add"
228app/models/endpoints.js:29 "convert"241app/models/endpoints.js:30 "convert"
229app/models/charm.js:155 "validator"242app/models/charm.js:157 "validator"
230app/models/charm.js:113 "parse"243app/models/charm.js:115 "parse"
231app/models/charm.js:77 "sync"244app/models/charm.js:79 "sync"
232app/models/charm.js:105 "failure"245app/models/charm.js:131 "compare"
233app/models/charm.js:48 "initializer"246app/models/charm.js:107 "failure"
234app/models/charm.js:129 "compare"247app/models/charm.js:50 "initializer"

Subscribers

People subscribed via source and target branches