Merge lp:~bcsaller/juju-gui/persistent-layout into lp:juju-gui/experimental

Proposed by Benjamin Saller
Status: Merged
Merged at revision: 332
Proposed branch: lp:~bcsaller/juju-gui/persistent-layout
Merge into: lp:juju-gui/experimental
Diff against target: 1142 lines (+498/-315) (has conflicts)
9 files modified
app/app.js (+6/-1)
app/models/models.js (+0/-1)
app/store/notifications.js (+12/-13)
app/views/environment.js (+21/-0)
app/views/topology/service.js (+249/-146)
test/test_environment_view.js (+44/-5)
test/test_service_module.js (+2/-2)
test/test_topology.js (+14/-2)
undocumented (+150/-145)
Text conflict in test/test_topology.js
To merge this branch: bzr merge lp:~bcsaller/juju-gui/persistent-layout
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+143980@code.launchpad.net

Description of the change

Support for service positions from server

Provides support for loading initial and updated position
from annotations stored on the server. Updates occur
via a shared codepath with the drag event handler
to ensure proper updates are applied over time.

There is still an issue with deltas interrupting the drag.

https://codereview.appspot.com/7132061/

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

Reviewers: mp+143980_code.launchpad.net,

Message:
Please take a look.

Description:
Support for service positions from server

Provides support for loading initial and updated position
from annotations stored on the server. Updates occur
via a shared codepath with the drag event handler
to ensure proper updates are applied over time.

https://code.launchpad.net/~bcsaller/juju-gui/persistent-layout/+merge/143980

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M app/app.js
   M app/models/models.js
   M app/views/environment.js
   M app/views/topology/service.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. Thank you for getting this in.

The branch has some conflicts. They don't look too bad, but they will take at least a bit of investigation. I might have a chance to try and resolve them Sunday so we can expedite the review.

Thanks

Gary

On Jan 19, 2013, at 2:13 AM, Benjamin Saller <email address hidden> wrote:

> Reviewers: mp+143980_code.launchpad.net,
>
> Message:
> Please take a look.
>
> Description:
> Support for service positions from server
>
> Provides support for loading initial and updated position
> from annotations stored on the server. Updates occur
> via a shared codepath with the drag event handler
> to ensure proper updates are applied over time.
>
> https://code.launchpad.net/~bcsaller/juju-gui/persistent-layout/+merge/143980
>
> (do not edit description out of merge proposal)
>
>
> Please review this at https://codereview.appspot.com/7132061/
>
> Affected files:
> A [revision details]
> M app/app.js
> M app/models/models.js
> M app/views/environment.js
> M app/views/topology/service.js
> M test/test_environment_view.js
> M test/test_service_module.js
> M test/test_topology.js
> M undocumented
>
>
>
> --
> https://code.launchpad.net/~bcsaller/juju-gui/persistent-layout/+merge/143980
> Your team Juju GUI Hackers is requested to review the proposed merge of lp:~bcsaller/juju-gui/persistent-layout into lp:juju-gui.

333. By Benjamin Saller

merge trunk

334. By Benjamin Saller

Don't fear the linter

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

Thanks for taking a peek, I resolved the merge and proposed again.

https://codereview.appspot.com/7132061/

Revision history for this message
Benjamin Saller (bcsaller) wrote :
Revision history for this message
Gary Poster (gary) wrote :
Download full text (4.6 KiB)

Hi Ben. Thank you for this branch. Land with changes (specifically
reinstating some of Nicola's changes that you lost, as mentioned below).

Tests pass for me.

The functionality works well, except for variants of the pre-existing
bug 1099921: moving services while the delta stream is being processed
leads to unexpected and undesired behavior. Either the drag is stopped,
or the drag completes but the processed location then moves the service
to where it was before. Hopefully we can get that addressed soon.

If it helps move things along in terms of Monday/Tuesday delivery, I
would be OK with you landing this and then responding to the
second/subsequent reviews in a separate branch. OTOH, if it doesn't
help you, don't do it. :-)

Thanks

Gary

https://codereview.appspot.com/7132061/diff/6001/app/app.js
File app/app.js (right):

https://codereview.appspot.com/7132061/diff/6001/app/app.js#newcode351
app/app.js:351: active.update();
cool

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

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode28
app/views/topology/service.js:28: mouseout: 'serviceStatusMouseOut'
Moving the functions out of the event registration does make it easier
to read. However, it makes the diff much harder to read. For instance,
AFAICT, there are no changes to these functions, but I'm coming to that
conclusion by eyeball rather than by mechanical diff. This would be the
kind of change I'd like to see in a separate branch to keep the branch
size down, to reduce the development time, and to ease reviews of the
pertinent code changes to actually implement this feature.

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode33
app/views/topology/service.js:33: * relation.
We now have this comment both in the event registration and in the
function definition. I'm not sure that inline event handler functions
don't have their place, to be honest. If we do separate them, I think
we can only expect to maintain comments in one place. I believe that
the place should be the function, so I would recommend deleting this
copy of the comment.

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode65
app/views/topology/service.js:65: toggleControlPanel: {callback:
function() {
I prefer the change that Nicola made, that you have reverted. In that
change, he renamed "ControlPanel" to "ServiceMenu," which seemed like a
naming improvement to Nicola and to his two reviewers. He also was able
to be explicit that we only needed to "hide" here, which would be nice
if we can manage it.

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode178
app/views/topology/service.js:178:
self.service_click_actions.toggleControlPanel(null, self);
You've reverted Nicola's changes. I think they were improvements, but
even if you disagree, socially a discussion would be better. Please
don't do this.

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode189
app/views/topology/service.js:189: .toggleControlPanel(box, con...

Read more...

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

Thanks for the review, I'll attempt to address the issues today.

https://codereview.appspot.com/7132061/

Revision history for this message
Francesco Banconi (frankban) wrote :

Hi Ben, thanks for this branch.
I have not so much to add to Gary's review. Please see just the one
comment below.

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

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode514
app/views/topology/service.js:514: topo.fire('serviceMoved', {service:
d});
Isn't serviceMoved already fired by self.drag?

https://codereview.appspot.com/7132061/

335. By Benjamin Saller

review changes

Revision history for this message
Benjamin Saller (bcsaller) wrote :
Download full text (4.0 KiB)

https://codereview.appspot.com/7132061/diff/6001/app/app.js
File app/app.js (right):

https://codereview.appspot.com/7132061/diff/6001/app/app.js#newcode351
app/app.js:351: active.update();
On 2013/01/20 15:58:45, gary.poster wrote:
> cool

Thanks, cutting to this should actually be a big improvement. I was
worried there would still be more work left before this would work
properly but it seems fine like this. This cuts the number of render
passes substantially and reduces the amount of work we do quite a lot.

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

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode33
app/views/topology/service.js:33: * relation.
On 2013/01/20 15:58:45, gary.poster wrote:
> We now have this comment both in the event registration and in the
function
> definition. I'm not sure that inline event handler functions don't
have their
> place, to be honest. If we do separate them, I think we can only
expect to
> maintain comments in one place. I believe that the place should be
the
> function, so I would recommend deleting this copy of the comment.

Agreed with comment placement, this was the result of trying to quickly
cut a smaller branch, bad porting.

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode65
app/views/topology/service.js:65: toggleControlPanel: {callback:
function() {
On 2013/01/20 15:58:45, gary.poster wrote:
> I prefer the change that Nicola made, that you have reverted. In that
change,
> he renamed "ControlPanel" to "ServiceMenu," which seemed like a naming
> improvement to Nicola and to his two reviewers. He also was able to
be explicit
> that we only needed to "hide" here, which would be nice if we can
manage it.

Not intentional, this is how the trunk merge fell out. I'll restore his
version (which I had a hand in as well and agreed with).

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode178
app/views/topology/service.js:178:
self.service_click_actions.toggleControlPanel(null, self);
On 2013/01/20 15:58:45, gary.poster wrote:
> You've reverted Nicola's changes. I think they were improvements, but
even if
> you disagree, socially a discussion would be better. Please don't do
this.

Bad Merge, fixed.

https://codereview.appspot.com/7132061/diff/6001/app/views/topology/service.js#newcode201
app/views/topology/service.js:201: .toggleControlPanel(box, context);
On 2013/01/20 15:58:45, gary.poster wrote:
> You've reverted Nicola's changes. Please don't do this.

Same

https://codereview.appspot.com/7132061/diff/6001/test/test_environment_view.js
File test/test_environment_view.js (right):

https://codereview.appspot.com/7132061/diff/6001/test/test_environment_view.js#newcode112
test/test_environment_view.js:112: db.on_delta({data:
Y.clone(environment_delta)});
This change may be worth of review comment. This is needed because we
delete annotations once their x/y has been read and applied. This
creates an issue in testing because its a reference all the way back to
the original test data stub.

https://codereview.appsp...

Read more...

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

*** Submitted:

Support for service positions from server

Provides support for loading initial and updated position
from annotations stored on the server. Updates occur
via a shared codepath with the drag event handler
to ensure proper updates are applied over time.

There is still an issue with deltas interrupting the drag.

R=gary.poster, frankban
CC=
https://codereview.appspot.com/7132061

https://codereview.appspot.com/7132061/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/app.js'
2--- app/app.js 2013-01-18 18:54:21 +0000
3+++ app/app.js 2013-01-21 15:11:21 +0000
4@@ -325,6 +325,7 @@
5 Y.log(evt, 'debug', 'App: Database changed');
6
7 var self = this;
8+ var active = this.get('activeView');
9
10 // Compare endpoints map against db to see if it needs to be changed.
11 var updateNeeded = this.db.services.some(function(service) {
12@@ -346,7 +347,11 @@
13 }
14
15 // Redispatch to current view to update.
16- this.dispatch();
17+ if (active && active.name === 'EnvironmentView') {
18+ active.update();
19+ } else {
20+ this.dispatch();
21+ }
22 },
23
24 /**
25
26=== modified file 'app/models/models.js'
27--- app/models/models.js 2012-11-20 16:55:43 +0000
28+++ app/models/models.js 2013-01-21 15:11:21 +0000
29@@ -32,7 +32,6 @@
30 Y.each(data, function(value, key) {
31 o[key] = value;
32 });
33- // XXX Fire model changed event manually if we need it later?
34 }
35 }
36 }
37
38=== modified file 'app/store/notifications.js'
39--- app/store/notifications.js 2012-11-16 08:25:02 +0000
40+++ app/store/notifications.js 2013-01-21 15:11:21 +0000
41@@ -146,7 +146,6 @@
42 * Process new delta stream events and see if we need new notifications
43 */
44 generate_notices: function(delta_evt) {
45- console.log('Generating Notices', this, this.getAttrs());
46 var self = this,
47 rules = this.ingest_rules,
48 app = this.get('app'),
49@@ -161,18 +160,18 @@
50 model;
51
52 /*
53- * Data ingestion rules
54- * Create notifications for incoming deltas
55- * Promote some notifications to the 'show me' list
56- * Also:
57- * - for each change event see if there is an notice
58- * relating to that object in the model list
59- * -- see if the current change event invalidates the need
60- * to show the existing notices
61- * -- make the new notice as 'must see' or not (
62- * errors, etc)
63- * - add a notification for the event
64- */
65+ * Data ingestion rules
66+ * Create notifications for incoming deltas
67+ * Promote some notifications to the 'show me' list
68+ * Also:
69+ * - for each change event see if there is an notice
70+ * relating to that object in the model list
71+ * -- see if the current change event invalidates the need
72+ * to show the existing notices
73+ * -- make the new notice as 'must see' or not (
74+ * errors, etc)
75+ * - add a notification for the event
76+ */
77
78 // Dispatch ingestion rules (which may mutate either the
79 // current 'notifications' or models within it (notice status)
80
81=== modified file 'app/views/environment.js'
82--- app/views/environment.js 2013-01-15 09:27:53 +0000
83+++ app/views/environment.js 2013-01-21 15:11:21 +0000
84@@ -28,6 +28,27 @@
85 preventable: false});
86 },
87
88+ /**
89+ * Wrapper around topo.update. Rather than
90+ * re-rendering a whole topology the view
91+ * can require data updates when needed.
92+ * Ideally even this shouldn't be needed
93+ * as we can observe ModelList change events
94+ * and debounce update calculations
95+ * internally.
96+ *
97+ * @method update
98+ * @chainable
99+ **/
100+ update: function() {
101+ this.topo.update();
102+ return this;
103+ },
104+
105+ /**
106+ * @method render
107+ * @chainable
108+ **/
109 render: function() {
110 var container = this.get('container'),
111 topo = this.topo;
112
113=== modified file 'app/views/topology/service.js'
114--- app/views/topology/service.js 2013-01-18 21:31:45 +0000
115+++ app/views/topology/service.js 2013-01-21 15:11:21 +0000
116@@ -24,29 +24,11 @@
117 },
118
119 '.service-status': {
120- mouseover: {callback: function(d, self) {
121- d3.select(this)
122- .select('.unit-count')
123- .attr('class', 'unit-count show-count');
124- }},
125- mouseout: {callback: function(d, self) {
126- d3.select(this)
127- .select('.unit-count')
128- .attr('class', 'unit-count hide-count');
129- }}
130+ mouseover: 'serviceStatusMouseOver',
131+ mouseout: 'serviceStatusMouseOut'
132 },
133- '.topology .crosshatch-background rect:first-child': {
134- /**
135- * If the user clicks on the background we cancel any active add
136- * relation.
137- */
138- click: {callback: function(d, self) {
139- var container = self.get('container'),
140- topo = self.get('component');
141- container.all('.environment-menu.active').removeClass('active');
142- self.service_click_actions.hideServiceMenu(null, self);
143- topo.fire('clearState');
144- }}
145+ '.zoomPlane': {
146+ click: 'zoomPlaneClick'
147 },
148 '.graph-list-picker .picker-button': {
149 click: 'showGraphListPicker'
150@@ -56,63 +38,16 @@
151 },
152 // Menu/Controls
153 '.view-service': {
154- /** The user clicked on the "View" menu item. */
155- click: {callback: function(data, context) {
156- // Get the service element
157- var topo = context.get('component');
158- var box = topo.get('active_service');
159- var service = topo.serviceForBox(box);
160- context.service_click_actions
161- .hideServiceMenu(box, context);
162- context.service_click_actions
163- .show_service(service, context);
164- }}
165+ click: 'viewServiceClick'
166 },
167 '.destroy-service': {
168- /** The user clicked on the "Destroy" menu item. */
169- click: {callback: function(data, context) {
170- // Get the service element
171- var topo = context.get('component');
172- var box = topo.get('active_service');
173- var service = topo.serviceForBox(box);
174- // The user is not allowed to destroy the Juju GUI service because
175- // it would break the application they are currently using.
176- if (utils.isGuiService(service)) {
177- return;
178- }
179- context.service_click_actions
180- .hideServiceMenu(box, context);
181- context.service_click_actions
182- .destroyServiceConfirm(service, context);
183- }}
184+ click: 'destroyServiceClick'
185 }
186 },
187 d3: {
188 '.service': {
189- 'mousedown.addrel': {callback: function(d, context) {
190- var evt = d3.event;
191- var topo = context.get('component');
192- context.longClickTimer = Y.later(750, this, function(d, e) {
193- // Provide some leeway for accidental dragging.
194- if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) /
195- 2 > 5) {
196- return;
197- }
198-
199- // Sometimes mouseover is fired after the mousedown, so ensure
200- // we have the correct event in d3.event for d3.mouse().
201- d3.event = e;
202-
203- // Start the process of adding a relation
204- topo.fire('addRelationDragStart', {service: d});
205- }, [d, evt], false);
206- }},
207- 'mouseup.addrel': {callback: function(d, context) {
208- // Cancel the long-click timer if it exists.
209- if (context.longClickTimer) {
210- context.longClickTimer.cancel();
211- }
212- }}
213+ 'mousedown.addrel': 'serviceAddRelMouseDown',
214+ 'mouseup.addrel': 'serviceAddRelMouseUp'
215 }
216 },
217 yui: {
218@@ -120,6 +55,9 @@
219 show: 'show',
220 hide: 'hide',
221 fade: 'fade',
222+ dragstart: 'dragstart',
223+ drag: 'drag',
224+ dragend: 'dragend',
225 hideServiceMenu: {callback: function() {
226 this.service_click_actions.hideServiceMenu(null, this);
227 }},
228@@ -127,6 +65,10 @@
229 }
230 },
231
232+ // Margins applied on update to Box instances.
233+ subordinate_margins: {top: 0.05, bottom: 0.1, left: 0.084848, right: 0.084848},
234+ service_margins: {top: 0, bottom: 0.1667, left: 0.086758, right: 0.086758},
235+
236 initializer: function(options) {
237 ServiceModule.superclass.constructor.apply(this, arguments);
238
239@@ -210,6 +152,82 @@
240 topo.fire('mouseMove');
241 },
242
243+ /**
244+ * Handle mouseover service status
245+ **/
246+ serviceStatusMouseOver: function(d, context) {
247+ d3.select(this)
248+ .select('.unit-count')
249+ .attr('class', 'unit-count show-count');
250+ },
251+
252+ serviceStatusMouseOut: function(d, context) {
253+ d3.select(this)
254+ .select('.unit-count')
255+ .attr('class', 'unit-count hide-count');
256+ },
257+
258+ /**
259+ * If the user clicks on the background we cancel any active add
260+ * relation.
261+ */
262+ zoomPlaneClick: function(d, self) {
263+ var container = self.get('container'),
264+ topo = self.get('component');
265+ container.all('.environment-menu.active').removeClass('active');
266+ self.service_click_actions.toggleControlPanel(null, self);
267+ topo.fire('clearState');
268+ },
269+
270+ /** The user clicked on the "View" menu item. */
271+ viewServiceClick: function(d, context) {
272+ // Get the service element
273+ var topo = context.get('component');
274+ var box = topo.get('active_service');
275+ var service = topo.serviceForBox(box);
276+ context.service_click_actions
277+ .toggleControlPanel(box, context);
278+ context.service_click_actions
279+ .show_service(service, context);
280+ },
281+
282+ /** The user clicked on the "Destroy" menu item. */
283+ destroyServiceClick: function(data, context) {
284+ // Get the service element
285+ var topo = context.get('component');
286+ var box = topo.get('active_service');
287+ var service = topo.serviceForBox(box);
288+ context.service_click_actions
289+ .toggleControlPanel(box, context);
290+ context.service_click_actions
291+ .destroyServiceConfirm(service, context);
292+ },
293+
294+ serviceAddRelMouseDown: function(d, context) {
295+ var evt = d3.event;
296+ var topo = context.get('component');
297+ context.longClickTimer = Y.later(750, this, function(d, e) {
298+ // Provide some leeway for accidental dragging.
299+ if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) /
300+ 2 > 5) {
301+ return;
302+ }
303+
304+ // Sometimes mouseover is fired after the mousedown, so ensure
305+ // we have the correct event in d3.event for d3.mouse().
306+ d3.event = e;
307+
308+ // Start the process of adding a relation
309+ topo.fire('addRelationDragStart', {service: d});
310+ }, [d, evt], false);
311+ },
312+
313+ serviceAddRelMouseUp: function(d, context) {
314+ // Cancel the long-click timer if it exists.
315+ if (context.longClickTimer) {
316+ context.longClickTimer.cancel();
317+ }
318+ },
319 /*
320 * Sync view models with current db.models.
321 */
322@@ -224,46 +242,122 @@
323
324 Y.each(services, function(service) {
325 // Update services with existing positions.
326+ // In the future it would be better to sync
327+ // the model to the existing box.
328 var existing = this.service_boxes[service.id];
329 if (existing) {
330 service.pos = existing.pos;
331+ service.inDrag = existing.inDrag;
332 }
333 service.margins(service.subordinate ?
334- {
335- top: 0.05,
336- bottom: 0.1,
337- left: 0.084848,
338- right: 0.084848} :
339- {
340- top: 0,
341- bottom: 0.1667,
342- left: 0.086758,
343- right: 0.086758});
344+ this.subordinate_margins :
345+ this.service_margins);
346 this.service_boxes[service.id] = service;
347 }, this);
348+
349+ // XXX: containment breaking alias, do we need this?
350 topo.service_boxes = this.service_boxes;
351
352 // Nodes are mapped by modelId tuples.
353 this.node = vis.selectAll('.service')
354- .data(services, function(d) { return d.modelId();});
355+ .data(services, function(d) {
356+ return d.modelId();});
357 },
358
359 /**
360- * Handle dragend events for a service.
361+ * Handle drag events for a service.
362 *
363 * @param {object} svc A service object.
364 * @param {object} i Unused.
365 * @return {undefined} Side effects only.
366 */
367- _dragend: function(d, i) {
368- var topo = this.get('component');
369+ dragstart: function(d, self) {
370+ var topo = self.get('component');
371+ d.oldX = d.x;
372+ d.oldY = d.y;
373+ self.get('container').all('.environment-menu.active')
374+ .removeClass('active');
375+ self.service_click_actions.hideServiceMenu(null, self);
376+ console.log('dragstart');
377+ },
378+
379+ dragend: function(d, self) {
380+ var topo = self.get('component');
381 if (topo.buildingRelation) {
382 topo.fire('addRelationDragEnd');
383 }
384- // Do not update annotations if the GUI is in read-only mode.
385- else if (!topo.get('env').get('readOnly')) {
386- topo.get('env').update_annotations(d.id, {'gui.x': d.x, 'gui.y': d.y});
387- }
388+ else {
389+ topo.get('env').update_annotations(
390+ d.id, {'gui.x': d.x, 'gui.y': d.y},
391+ function() {
392+ // Force a reposition at the end.
393+ d.inDrag = false;
394+ //self.drag.call(self.getServiceNode(d.id),
395+ // d, self, {x:d.x, y: d.y}, false);
396+ });
397+ }
398+ console.log('dragend');
399+ },
400+
401+ /**
402+ * Specialized drag event handler
403+ * when called as an event handler it
404+ * Allows optional extra param, pos
405+ * which when used overrides the mouse
406+ * handling. This method can then be
407+ * though of as 'drag to position'.
408+ *
409+ * @method drag
410+ * @param {Box} d viewModel BoundingBox.
411+ * @param {ServiceModule} self ServiceModule.
412+ * @param {Object} pos (optional) containing x/y numbers.
413+ * @param {Boolean} includeTransition (optional) Use transition to drag.
414+ *
415+ * [At the time of this writing useTransition works in practice but
416+ * introduces a timing issue in the tests.]
417+ **/
418+ drag: function(d, self, pos, includeTransition) {
419+ var topo = self.get('component');
420+ var selection = d3.select(this);
421+
422+ if (topo.buildingRelation) {
423+ topo.fire('addRelationDrag', { box: d });
424+ return;
425+ }
426+ if (self.longClickTimer) {
427+ self.longClickTimer.cancel();
428+ }
429+ // Translate the service (and, potentially, menu).
430+ if (pos) {
431+ d.x = pos.x;
432+ d.y = pos.y;
433+ // Explicitly reassign data.
434+ selection = selection.data([d]);
435+ } else {
436+ d.x += d3.event.dx;
437+ d.y += d3.event.dy;
438+ }
439+
440+ if (includeTransition) {
441+ selection = selection.transition()
442+ .duration(500)
443+ .ease('elastic');
444+ }
445+
446+ selection.attr('transform', function(d, i) {
447+ return d.translateStr();
448+ });
449+ if (topo.get('active_service') === d) {
450+ self.updateServiceMenuLocation();
451+ }
452+
453+ // Clear any state while dragging.
454+ self.get('container').all('.environment-menu.active')
455+ .removeClass('active');
456+ topo.fire('cancelRelationBuild');
457+ // Update relation lines for just this service.
458+ topo.fire('serviceMoved', { service: d });
459+ console.log('drag');
460 },
461
462 /*
463@@ -291,46 +385,16 @@
464 .padding(300);
465 }
466
467+ if (!this.dragBehavior) {
468+ this.dragBehavior = d3.behavior.drag()
469+ .on('dragstart', function(d) { self.dragstart.call(this, d, self);})
470+ .on('drag', function(d) { self.drag.call(this, d, self);})
471+ .on('dragend', function(d) { self.dragend.call(this, d, self);});
472+ }
473+
474 //Process any changed data.
475 this.updateData();
476
477- var drag = d3.behavior.drag()
478- .on('dragstart', function(d) {
479- d.oldX = d.x;
480- d.oldY = d.y;
481- self.get('container').all('.environment-menu.active')
482- .removeClass('active');
483- self.service_click_actions.hideServiceMenu(null, self);
484- })
485- .on('drag', function(d, i) {
486- if (topo.buildingRelation) {
487- topo.fire('addRelationDrag', { box: d });
488- } else {
489- if (self.longClickTimer) {
490- self.longClickTimer.cancel();
491- }
492-
493- // Translate the service (and, potentially, menu).
494- d.x += d3.event.dx;
495- d.y += d3.event.dy;
496- d3.select(this).attr('transform', function(d, i) {
497- return d.translateStr();
498- });
499- if (topo.get('active_service') === d) {
500- self.updateServiceMenuLocation();
501- }
502-
503- // Clear any state while dragging.
504- self.get('container').all('.environment-menu.active')
505- .removeClass('active');
506- topo.fire('cancelRelationBuild');
507-
508- // Update relation lines for just this service.
509- topo.fire('serviceMoved', { service: d });
510- }
511- })
512- .on('dragend', Y.bind(this._dragend, this));
513-
514 // Generate a node for each service, draw it as a rect with
515 // labels for service and charm.
516 var node = this.node;
517@@ -344,32 +408,49 @@
518 var new_services = this.services.filter(function(boundingBox) {
519 return !Y.Lang.isNumber(boundingBox.x);
520 });
521- this.tree.nodes({children: new_services});
522-
523+ if (new_services) {
524+ this.tree.nodes({children: new_services});
525+ }
526 // enter
527 node
528 .enter().append('g')
529 .attr('class', function(d) {
530 return (d.subordinate ? 'subordinate ' : '') + 'service';
531 })
532- .call(drag)
533+ .call(this.dragBehavior)
534 .attr('transform', function(d) {
535 return d.translateStr();
536 })
537- .call(function() {
538- // Create new nodes.
539- self.createServiceNode(this);
540- });
541+ .call(self.createServiceNode);
542
543 // Update all nodes.
544 self.updateServiceNodes(node);
545
546 // Remove old nodes.
547 node.exit()
548+ .each(function(d) {
549+ delete self.service_boxes[d.id];
550+ })
551 .remove();
552 },
553
554 /**
555+ * Get a d3 selected node for a given service by id.
556+ *
557+ * @method getServiceNode
558+ * @return {d3.selection} selection || null.
559+ **/
560+ getServiceNode: function(id) {
561+ if (this.node === undefined) {
562+ return null;
563+ }
564+ var node = this.node.filter(function(d, i) {
565+ return d.id === id;
566+ });
567+ return node && node[0][0] || null;
568+ },
569+
570+ /**
571 * @method createServiceNode fills a service node with empty structures
572 * that will be filled out in the update stage.
573 * @param {object} node the node to construct.
574@@ -415,6 +496,30 @@
575 service_scale_width = this.service_scale_width,
576 service_scale_height = this.service_scale_height;
577
578+ // Apply Position Annotations
579+ // This is done after the services_boxes
580+ // binding as the event handler will
581+ // use that index.
582+ node.each(function(d) {
583+ var service = topo.serviceForBox(d),
584+ annotations = service.get('annotations'),
585+ x, y;
586+
587+ if (!annotations) {return;}
588+ x = annotations['gui.x'],
589+ y = annotations['gui.y'];
590+ if (!d ||
591+ (x !== undefined && x !== d.x) &&
592+ (y !== undefined && y !== d.y)) {
593+ // Delete gui.x and gui.y from annotations
594+ // as we use the values.
595+ delete annotations['gui.x'];
596+ delete annotations['gui.y'];
597+ if (!d.inDrag) {
598+ self.drag.call(this, d, self, {x: x, y: y});
599+ }
600+ }});
601+
602 // Size the node for drawing.
603 node.attr('width', function(d) {
604 // NB: if a service has zero units, as is possible with
605@@ -659,8 +764,6 @@
606 renderedHandler: function() {
607 var container = this.get('container');
608
609- this.update();
610-
611 // Ensure relation labels are sized properly.
612 container.all('.rel-label').each(function(label) {
613 var width = label.one('text').getClientRect().width + 10;
614@@ -769,13 +872,13 @@
615 *
616 * @method showServiceMenu
617 * @param {object} box The presentation state for the service.
618- * @param {object} view The environment view.
619+ * @param {object} module The service module..
620 * @param {object} context The service context.
621 * @return {undefined} Side effects only.
622 */
623- showServiceMenu: function(box, view, context) {
624- var svc_menu = view.get('container').one('#service-menu');
625- var topo = view.get('component');
626+ showServiceMenu: function(box, module, context) {
627+ var svc_menu = module.get('container').one('#service-menu');
628+ var topo = module.get('component');
629 var service = topo.serviceForBox(box);
630
631 if (box && !svc_menu.hasClass('active')) {
632@@ -786,7 +889,7 @@
633 if (utils.isGuiService(service)) {
634 svc_menu.one('.destroy-service').addClass('disabled');
635 }
636- view.updateServiceMenuLocation();
637+ module.updateServiceMenuLocation();
638 }
639 },
640
641@@ -795,13 +898,13 @@
642 *
643 * @method hideServiceMenu
644 * @param {object} box The presentation state for the service (unused).
645- * @param {object} view The environment view.
646+ * @param {object} module The service module.
647 * @param {object} context The service context (unused).
648 * @return {undefined} Side effects only.
649 */
650- hideServiceMenu: function(box, view, context) {
651- var svc_menu = view.get('container').one('#service-menu');
652- var topo = view.get('component');
653+ hideServiceMenu: function(box, module, context) {
654+ var svc_menu = module.get('container').one('#service-menu');
655+ var topo = module.get('component');
656
657 if (svc_menu.hasClass('active')) {
658 svc_menu.removeClass('active');
659
660=== modified file 'test/test_environment_view.js'
661--- test/test_environment_view.js 2013-01-18 10:15:33 +0000
662+++ test/test_environment_view.js 2013-01-21 15:11:21 +0000
663@@ -11,7 +11,8 @@
664 ['service', 'add', {
665 'charm': 'cs:precise/wordpress-6',
666 'id': 'wordpress',
667- 'exposed': false
668+ 'exposed': false,
669+ 'annotations': {'gui.x': 100, 'gui.y': 200}
670 }],
671 ['service', 'add', {
672 'charm': 'cs:precise/mediawiki-3',
673@@ -92,7 +93,6 @@
674 env = new juju.Environment({conn: conn});
675 env.connect();
676 conn.open();
677- env.dispatch_result(environment_delta);
678 done();
679 });
680 });
681@@ -102,12 +102,14 @@
682 done();
683 });
684
685- beforeEach(function(done) {
686+ beforeEach(function() {
687 container = Y.Node.create('<div />').setStyle('visibility', 'hidden');
688 Y.one('body').prepend(container);
689 db = new models.Database();
690- db.on_delta({data: environment_delta});
691- done();
692+ // Use a clone to avoid any mutation
693+ // to the input set (as happens with processed
694+ // annotations, its a direct reference).
695+ db.on_delta({data: Y.clone(environment_delta)});
696 });
697
698 afterEach(function(done) {
699@@ -396,6 +398,43 @@
700 });
701 });
702
703+ it('must be able to use position annotations',
704+ function() {
705+ var view = new views.environment({
706+ container: container,
707+ db: db,
708+ env: env
709+ });
710+ var tmp_data = {
711+ op: 'delta',
712+ result: [
713+ ['service', 'add',
714+ {
715+ 'subordinate': true,
716+ 'charm': 'cs:precise/wordpress-6',
717+ 'id': 'wordpress',
718+ 'annotations': {'gui.x': 374.1, 'gui.y': 211.2}
719+ }]]};
720+ var properTransform = /translate\((\d+\.?\d*),(\d+\.?\d*)\)/;
721+ var node, match;
722+
723+ view.render();
724+
725+ // Test values from initial load.
726+ node = view.topo.modules.ServiceModule.getServiceNode('wordpress');
727+ match = node.getAttribute('transform').match(properTransform);
728+ match[1].should.eql('100');
729+ match[2].should.eql('200');
730+
731+ db.on_delta({ data: tmp_data });
732+ view.update();
733+
734+ //On annotation change position should be updated.
735+ match = node.getAttribute('transform').match(properTransform);
736+ match[1].should.eql('374.1');
737+ match[2].should.eql('211.2');
738+ });
739+
740 it('must be able to render subordinate relation indicators',
741 function() {
742 var view = new views.environment({
743
744=== modified file 'test/test_service_module.js'
745--- test/test_service_module.js 2013-01-18 18:42:37 +0000
746+++ test/test_service_module.js 2013-01-21 15:11:21 +0000
747@@ -56,7 +56,7 @@
748 { id: 'wordpress',
749 x: 100.1,
750 y: 200.2};
751- serviceModule._dragend(d, 0);
752+ serviceModule.dragend(d, serviceModule);
753 assert.isTrue(called);
754 location['gui.x'].should.equal(100.1);
755 location['gui.y'].should.equal(200.2);
756@@ -70,7 +70,7 @@
757 y: 200.2};
758 var topo = serviceModule.get('component');
759 topo.buildingRelation = true;
760- serviceModule._dragend(d, 0);
761+ serviceModule.dragend(d, serviceModule);
762 assert.isFalse(called);
763 location['gui.x'].should.equal(0);
764 location['gui.y'].should.equal(0);
765
766=== modified file 'test/test_topology.js'
767--- test/test_topology.js 2013-01-21 13:43:21 +0000
768+++ test/test_topology.js 2013-01-21 15:11:21 +0000
769@@ -113,10 +113,13 @@
770 if (name === 'component') {
771 return fauxTopo;
772 }
773+ },
774+ service_click_actions: {
775+ toggleControlPanel: function() {},
776+ destroyServiceConfirm: function() {}
777 }
778 };
779- topo.events.ServiceModule.scene['.destroy-service'].click.callback(
780- undefined, context);
781+ topo.modules.ServiceModule.destroyServiceClick(undefined, context);
782 });
783 });
784
785@@ -153,9 +156,18 @@
786 if (name === 'container') {
787 return {one: function() { return menu; }};
788 } else if (name === 'component') {
789+<<<<<<< TREE
790 return { set: function() {},
791 serviceForBox: function(box) { return service;}
792 };
793+=======
794+ return {
795+ set: function() {},
796+ serviceForBox: function() {
797+ return service;
798+ }
799+ };
800+>>>>>>> MERGE-SOURCE
801 }
802 },
803 updateServiceMenuLocation: function() {}
804
805=== modified file 'undocumented'
806--- undocumented 2013-01-18 18:42:37 +0000
807+++ undocumented 2013-01-21 15:11:21 +0000
808@@ -1,18 +1,18 @@
809-app/app.js:599 "callback"
810 app/app.js:100 "callback"
811-app/store/env.js:223 "status"
812-app/store/env.js:175 "get_service"
813-app/store/env.js:69 "on_open"
814-app/store/env.js:27 "initializer"
815-app/store/env.js:78 "on_message"
816-app/store/env.js:73 "on_close"
817-app/store/env.js:171 "get_charm"
818-app/store/env.js:45 "destructor"
819-app/store/env.js:111 "dispatch_result"
820-app/store/env.js:117 "_dispatch_event"
821-app/store/env.js:126 "_dispatch_rpc_result"
822-app/store/env.js:268 "get_endpoints"
823-app/store/env.js:50 "connect"
824+app/app.js:648 "callback"
825+app/store/env.js:31 "initializer"
826+app/store/env.js:239 "get_service"
827+app/store/env.js:55 "connect"
828+app/store/env.js:83 "on_message"
829+app/store/env.js:74 "on_open"
830+app/store/env.js:235 "get_charm"
831+app/store/env.js:78 "on_close"
832+app/store/env.js:116 "dispatch_result"
833+app/store/env.js:415 "get_endpoints"
834+app/store/env.js:320 "status"
835+app/store/env.js:131 "_dispatch_rpc_result"
836+app/store/env.js:50 "destructor"
837+app/store/env.js:122 "_dispatch_event"
838 app/store/charm.js:66 "_normalizeCharms"
839 app/store/charm.js:24 "find"
840 app/store/charm.js:11 "success"
841@@ -27,38 +27,39 @@
842 app/store/notifications.js:25 "message"
843 app/store/notifications.js:129 "title"
844 app/store/notifications.js:137 "message"
845-app/views/utils.js:370 "_addAlertMessage"
846-app/views/utils.js:825 "BoxPair"
847-app/views/utils.js:702 "scale"
848-app/views/utils.js:227 "humanizeNumber"
849-app/views/utils.js:137 "console"
850-app/views/utils.js:828 "pair"
851+app/views/utils.js:166 "substitute"
852+app/views/utils.js:251 "hasSVGClass"
853+app/views/utils.js:296 "toggleSVGClass"
854+app/views/utils.js:616 "BoundingBox"
855+app/views/utils.js:135 "noop"
856+app/views/utils.js:259 "addSVGClass"
857+app/views/utils.js:642 "get"
858+app/views/utils.js:138 "console"
859+app/views/utils.js:703 "scale"
860+app/views/utils.js:704 "translate"
861+app/views/utils.js:218 "renderable_charm"
862+app/views/utils.js:826 "BoxPair"
863 app/views/utils.js:113 "noop"
864-app/views/utils.js:617 "Box"
865-app/views/utils.js:250 "hasSVGClass"
866-app/views/utils.js:338 "action"
867-app/views/utils.js:134 "noop"
868-app/views/utils.js:658 "get"
869-app/views/utils.js:217 "renderable_charm"
870-app/views/utils.js:651 "set"
871-app/views/utils.js:641 "get"
872-app/views/utils.js:566 "isInt"
873-app/views/utils.js:615 "BoundingBox"
874-app/views/utils.js:570 "isFloat"
875-app/views/utils.js:650 "get"
876-app/views/utils.js:703 "translate"
877-app/views/utils.js:418 "invokeCallback"
878-app/views/utils.js:559 "toString"
879-app/views/utils.js:295 "toggleSVGClass"
880-app/views/utils.js:258 "addSVGClass"
881-app/views/utils.js:659 "set"
882-app/views/utils.js:279 "removeSVGClass"
883-app/views/utils.js:194 "bindModelView"
884-app/views/utils.js:165 "substitute"
885-app/views/utils.js:644 "set"
886-app/views/utils.js:131 "native"
887+app/views/utils.js:280 "removeSVGClass"
888+app/views/utils.js:371 "_addAlertMessage"
889+app/views/utils.js:829 "pair"
890+app/views/utils.js:228 "humanizeNumber"
891+app/views/utils.js:339 "action"
892+app/views/utils.js:567 "isInt"
893+app/views/utils.js:195 "bindModelView"
894+app/views/utils.js:618 "Box"
895+app/views/utils.js:651 "get"
896+app/views/utils.js:560 "toString"
897+app/views/utils.js:645 "set"
898+app/views/utils.js:419 "invokeCallback"
899+app/views/utils.js:571 "isFloat"
900+app/views/utils.js:132 "native"
901+app/views/utils.js:652 "set"
902+app/views/utils.js:660 "set"
903+app/views/utils.js:659 "get"
904 app/views/environment.js:24 "initializer"
905-app/views/environment.js:31 "render"
906+app/views/environment.js:31 "update"
907+app/views/environment.js:36 "render"
908 app/views/charm-panel.js:1168 "calculatePanelPosition"
909 app/views/charm-panel.js:476 "initializer"
910 app/views/charm-panel.js:250 "render"
911@@ -111,126 +112,130 @@
912 app/views/unit.js:297 "retryRelation"
913 app/views/unit.js:118 "confirmResolved"
914 app/views/unit.js:23 "initializer"
915-app/views/service.js:488 "updateConstraints"
916-app/views/service.js:514 "_setConstraintsCallback"
917-app/views/service.js:846 "filterUnits"
918+app/views/service.js:875 "render"
919+app/views/service.js:338 "fitToWindow"
920+app/views/service.js:688 "render"
921 app/views/service.js:288 "initializer"
922-app/views/service.js:547 "render"
923-app/views/service.js:372 "render"
924-app/views/service.js:451 "_removeRelationCallback"
925+app/views/service.js:740 "saveConfig"
926 app/views/service.js:191 "_destroyCallback"
927-app/views/service.js:338 "fitToWindow"
928 app/views/service.js:116 "_removeUnitCallback"
929-app/views/service.js:737 "_setConfigCallback"
930-app/views/service.js:402 "confirmRemoved"
931 app/views/service.js:264 "_exposeServiceCallback"
932-app/views/service.js:667 "showErrors"
933 app/views/service.js:257 "exposeService"
934-app/views/service.js:800 "render"
935+app/views/service.js:888 "filterUnits"
936+app/views/service.js:770 "_setConfigCallback"
937 app/views/service.js:52 "_modifyUnits"
938-app/views/service.js:707 "saveConfig"
939-app/views/service.js:608 "render"
940+app/views/service.js:406 "render"
941+app/views/service.js:418 "confirmRemoved"
942 app/views/service.js:339 "getHeight"
943 app/views/service.js:182 "destroyService"
944+app/views/service.js:440 "doRemoveRelation"
945+app/views/service.js:615 "render"
946 app/views/service.js:30 "modifyUnits"
947-app/views/service.js:424 "doRemoveRelation"
948 app/views/service.js:300 "getServiceTabs"
949 app/views/service.js:166 "confirmDestroy"
950+app/views/service.js:502 "updateConstraints"
951+app/views/service.js:528 "_setConstraintsCallback"
952 app/views/service.js:88 "_addUnitCallback"
953 app/views/service.js:23 "resetUnits"
954+app/views/service.js:700 "showErrors"
955 app/views/service.js:230 "unexposeService"
956 app/views/service.js:237 "_unexposeServiceCallback"
957-app/views/topology/panzoom.js:94 "zoom_out"
958+app/views/service.js:467 "_removeRelationCallback"
959+app/views/topology/panzoom.js:151 "rescale"
960 app/views/topology/panzoom.js:77 "zoomHandler"
961 app/views/topology/panzoom.js:47 "renderSlider"
962-app/views/topology/panzoom.js:112 "_fire_zoom"
963-app/views/topology/panzoom.js:165 "renderedHandler"
964-app/views/topology/panzoom.js:103 "zoom_in"
965-app/views/topology/panzoom.js:140 "rescale"
966+app/views/topology/panzoom.js:114 "zoom_in"
967+app/views/topology/panzoom.js:176 "renderedHandler"
968+app/views/topology/panzoom.js:123 "_fire_zoom"
969+app/views/topology/panzoom.js:105 "zoom_out"
970 app/views/topology/panzoom.js:33 "componentBound"
971-app/views/topology/relation.js:78 "processRelation"
972-app/views/topology/relation.js:403 "_removeRelationCallback"
973-app/views/topology/relation.js:325 "addRelationDragStart"
974-app/views/topology/relation.js:460 "cancelRelationBuild"
975-app/views/topology/relation.js:391 "removeRelation"
976-app/views/topology/relation.js:309 "snapOutOfService"
977-app/views/topology/relation.js:724 "subRelBlockMouseEnter"
978-app/views/topology/relation.js:632 "addRelationEnd"
979-app/views/topology/relation.js:350 "addRelationDrag"
980-app/views/topology/relation.js:280 "snapToService"
981-app/views/topology/relation.js:251 "draglineClicked"
982-app/views/topology/relation.js:158 "drawRelationGroup"
983-app/views/topology/relation.js:112 "updateLinks"
984-app/views/topology/relation.js:428 "removeRelationConfirm"
985-app/views/topology/relation.js:236 "updateSubordinateRelationsCount"
986-app/views/topology/relation.js:55 "render"
987-app/views/topology/relation.js:270 "addRelation"
988-app/views/topology/relation.js:257 "addRelButtonClicked"
989-app/views/topology/relation.js:781 "relationClick"
990-app/views/topology/relation.js:673 "_addRelationCallback"
991-app/views/topology/relation.js:717 "subordinateRelationsForService"
992-app/views/topology/relation.js:372 "addRelationDragEnd"
993-app/views/topology/relation.js:90 "processRelations"
994-app/views/topology/relation.js:50 "initializer"
995-app/views/topology/relation.js:536 "addRelationStart"
996-app/views/topology/relation.js:733 "subRelBlockMouseLeave"
997-app/views/topology/relation.js:60 "update"
998-app/views/topology/relation.js:221 "drawRelation"
999-app/views/topology/relation.js:74 "renderedHandler"
1000-app/views/topology/relation.js:548 "ambiguousAddRelationCheck"
1001-app/views/topology/service.js:635 "renderedHandler"
1002-app/views/topology/service.js:181 "serviceMouseLeave"
1003-app/views/topology/service.js:604 "show"
1004-app/views/topology/service.js:219 "updateData"
1005-app/views/topology/service.js:668 "showGraphListPicker"
1006-app/views/topology/service.js:610 "hide"
1007-app/views/topology/service.js:747 "show_service"
1008-app/views/topology/service.js:616 "fade"
1009-app/views/topology/service.js:128 "initializer"
1010-app/views/topology/service.js:156 "serviceDblClick"
1011-app/views/topology/service.js:138 "serviceClick"
1012-app/views/topology/service.js:258 "update"
1013-app/views/topology/service.js:163 "serviceMouseEnter"
1014-app/views/topology/service.js:779 "destroyService"
1015-app/views/topology/service.js:678 "hideGraphListPicker"
1016-app/views/topology/service.js:788 "_destroyCallback"
1017-app/views/topology/service.js:756 "destroyServiceConfirm"
1018-app/views/topology/service.js:685 "updateServiceMenuLocation"
1019-app/views/topology/topology.js:137 "serviceForBox"
1020-app/views/topology/topology.js:175 "setter"
1021-app/views/topology/topology.js:174 "getter"
1022-app/views/topology/topology.js:107 "computeScales"
1023-app/views/topology/topology.js:157 "getter"
1024+app/views/topology/relation.js:269 "addRelButtonClicked"
1025+app/views/topology/relation.js:101 "processRelations"
1026+app/views/topology/relation.js:362 "snapOutOfService"
1027+app/views/topology/relation.js:456 "_removeRelationCallback"
1028+app/views/topology/relation.js:513 "cancelRelationBuild"
1029+app/views/topology/relation.js:55 "initializer"
1030+app/views/topology/relation.js:247 "updateSubordinateRelationsCount"
1031+app/views/topology/relation.js:262 "draglineClicked"
1032+app/views/topology/relation.js:169 "drawRelationGroup"
1033+app/views/topology/relation.js:123 "updateLinks"
1034+app/views/topology/relation.js:378 "addRelationDragStart"
1035+app/views/topology/relation.js:834 "relationClick"
1036+app/views/topology/relation.js:85 "renderedHandler"
1037+app/views/topology/relation.js:601 "ambiguousAddRelationCheck"
1038+app/views/topology/relation.js:481 "removeRelationConfirm"
1039+app/views/topology/relation.js:60 "render"
1040+app/views/topology/relation.js:770 "subordinateRelationsForService"
1041+app/views/topology/relation.js:403 "addRelationDrag"
1042+app/views/topology/relation.js:425 "addRelationDragEnd"
1043+app/views/topology/relation.js:89 "processRelation"
1044+app/views/topology/relation.js:232 "drawRelation"
1045+app/views/topology/relation.js:685 "addRelationEnd"
1046+app/views/topology/relation.js:290 "addRelation"
1047+app/views/topology/relation.js:726 "_addRelationCallback"
1048+app/views/topology/relation.js:589 "addRelationStart"
1049+app/views/topology/relation.js:786 "subRelBlockMouseLeave"
1050+app/views/topology/relation.js:444 "removeRelation"
1051+app/views/topology/relation.js:333 "snapToService"
1052+app/views/topology/relation.js:65 "update"
1053+app/views/topology/relation.js:777 "subRelBlockMouseEnter"
1054+app/views/topology/service.js:727 "show"
1055+app/views/topology/service.js:799 "hideGraphListPicker"
1056+app/views/topology/service.js:72 "initializer"
1057+app/views/topology/service.js:288 "dragend"
1058+app/views/topology/service.js:789 "showGraphListPicker"
1059+app/views/topology/service.js:947 "destroyService"
1060+app/views/topology/service.js:206 "serviceAddRelMouseDown"
1061+app/views/topology/service.js:110 "serviceMouseEnter"
1062+app/views/topology/service.js:225 "serviceAddRelMouseUp"
1063+app/views/topology/service.js:164 "serviceStatusMouseOut"
1064+app/views/topology/service.js:739 "fade"
1065+app/views/topology/service.js:234 "updateData"
1066+app/views/topology/service.js:82 "serviceClick"
1067+app/views/topology/service.js:733 "hide"
1068+app/views/topology/service.js:758 "renderedHandler"
1069+app/views/topology/service.js:915 "show_service"
1070+app/views/topology/service.js:924 "destroyServiceConfirm"
1071+app/views/topology/service.js:364 "update"
1072+app/views/topology/service.js:128 "serviceMouseLeave"
1073+app/views/topology/service.js:956 "_destroyCallback"
1074+app/views/topology/service.js:100 "serviceDblClick"
1075+app/views/topology/service.js:806 "updateServiceMenuLocation"
1076+app/views/topology/topology.js:163 "getter"
1077+app/views/topology/topology.js:172 "setter"
1078+app/views/topology/topology.js:159 "getter"
1079+app/views/topology/topology.js:109 "computeScales"
1080+app/views/topology/topology.js:176 "getter"
1081+app/views/topology/topology.js:171 "getter"
1082+app/views/topology/topology.js:139 "serviceForBox"
1083+app/views/topology/topology.js:177 "setter"
1084+app/views/topology/topology.js:26 "initializer"
1085 app/views/topology/topology.js:63 "renderOnce"
1086-app/views/topology/topology.js:26 "initializer"
1087-app/views/topology/topology.js:161 "getter"
1088-app/views/topology/topology.js:169 "getter"
1089-app/views/topology/topology.js:170 "setter"
1090-app/views/topology/viewport.js:43 "resized"
1091-app/models/models.js:305 "setter"
1092-app/models/models.js:430 "getModelListByModelName"
1093-app/models/models.js:325 "add"
1094-app/models/models.js:171 "update_service_unit_aggregates"
1095-app/models/models.js:358 "getNotificationsForModel"
1096-app/models/models.js:69 "process_delta"
1097-app/models/models.js:456 "on_delta"
1098-app/models/models.js:421 "getModelById"
1099-app/models/models.js:149 "get_informative_states_for_service"
1100-app/models/models.js:204 "process_delta"
1101-app/models/models.js:339 "trim"
1102-app/models/models.js:385 "initializer"
1103-app/models/models.js:437 "getModelFromChange"
1104-app/models/models.js:273 "get_relations_for_service"
1105-app/models/models.js:330 "comparator"
1106-app/models/models.js:120 "add"
1107-app/models/models.js:112 "_setDefaultsAndCalculatedValues"
1108-app/models/models.js:297 "valueFn"
1109-app/models/models.js:446 "reset"
1110-app/models/models.js:130 "get_units_for_service"
1111-app/models/models.js:108 "process_delta"
1112-app/models/models.js:345 "removeOldest"
1113-app/models/models.js:240 "has_relation_for_endpoint"
1114-app/models/models.js:231 "process_delta"
1115+app/views/topology/viewport.js:31 "resized"
1116+app/models/models.js:357 "getNotificationsForModel"
1117+app/models/models.js:429 "getModelListByModelName"
1118+app/models/models.js:420 "getModelById"
1119+app/models/models.js:68 "process_delta"
1120+app/models/models.js:445 "reset"
1121+app/models/models.js:304 "setter"
1122+app/models/models.js:119 "add"
1123+app/models/models.js:384 "initializer"
1124+app/models/models.js:344 "removeOldest"
1125+app/models/models.js:455 "on_delta"
1126+app/models/models.js:272 "get_relations_for_service"
1127+app/models/models.js:436 "getModelFromChange"
1128+app/models/models.js:107 "process_delta"
1129+app/models/models.js:148 "get_informative_states_for_service"
1130+app/models/models.js:170 "update_service_unit_aggregates"
1131+app/models/models.js:230 "process_delta"
1132+app/models/models.js:129 "get_units_for_service"
1133+app/models/models.js:296 "valueFn"
1134+app/models/models.js:111 "_setDefaultsAndCalculatedValues"
1135+app/models/models.js:329 "comparator"
1136+app/models/models.js:203 "process_delta"
1137+app/models/models.js:239 "has_relation_for_endpoint"
1138+app/models/models.js:324 "add"
1139+app/models/models.js:338 "trim"
1140 app/models/endpoints.js:43 "add"
1141 app/models/endpoints.js:32 "convert"
1142 app/models/charm.js:155 "validator"

Subscribers

People subscribed via source and target branches