Merge lp:~frankban/juju-gui/inspector-who into lp:juju-gui/experimental

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 1125
Proposed branch: lp:~frankban/juju-gui/inspector-who
Merge into: lp:juju-gui/experimental
Diff against target: 380 lines (+247/-41)
5 files modified
app/views/utils.js (+59/-0)
app/views/viewlets/unit-details.js (+32/-32)
lib/views/browser/browser-icon.less (+1/-1)
test/test_unit_detail_viewlet.js (+40/-8)
test/test_utils.js (+115/-0)
To merge this branch: bzr merge lp:~frankban/juju-gui/inspector-who
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+190379@code.launchpad.net

Description of the change

Fix links in the unit details view.

Handle the juju-core "port/protocol" ports.
Privilege the HTTPS port if applicable.

Also fixed the z-index of the charm browser tab,
so that when the sidebar and the unit details view
appear together in a narrow browser it is still
possible to close the sidebar.

QA:
- deploy the GUI with this branch in a juju-core env:
  juju bootstrap --debug
  juju deploy juju-gui
  juju set juju-gui juju-gui-source=lp:~frankban/juju-gui/inspector-who
- wait for the unit to be ready;
- visit the juju-gui unit detail view;

You should be able to close the sidebar view.
The GUI address links to the right URL (https://).
The ports link to the right URLs without the trailing "/tcp".

https://codereview.appspot.com/14439054/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+190379_code.launchpad.net,

Message:
Please take a look.

Description:
Fix links in the unit details view.

Handle the juju-core "port/protocol" ports.
Privilege the HTTPS port if applicable.

Also fixed the z-index of the charm browser tab,
so that when the sidebar and the unit details view
appear together in a narrow browser it is still
possible to close the sidebar.

QA:
- deploy the GUI with this branch in a juju-core env:
   juju bootstrap --debug
   juju deploy juju-gui
   juju set juju-gui juju-gui-source=lp:~frankban/juju-gui/inspector-who
- wait for the unit to be ready;
- visit the juju-gui unit detail view;

You should be able to close the sidebar view.
The GUI address links to the right URL (https://).
The ports link to the right URLs without the trailing "/tcp".

https://code.launchpad.net/~frankban/juju-gui/inspector-who/+merge/190379

(do not edit description out of merge proposal)

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

Affected files (+249, -41 lines):
   A [revision details]
   M app/views/utils.js
   M app/views/viewlets/unit-details.js
   M lib/views/browser/browser-icon.less
   M test/test_unit_detail_viewlet.js
   M test/test_utils.js

Revision history for this message
Jeff Pihach (hatch) wrote :

Thanks for this - great tests!
QA'd on local provider - all looks good

https://codereview.appspot.com/14439054/

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

*** Submitted:

Fix links in the unit details view.

Handle the juju-core "port/protocol" ports.
Privilege the HTTPS port if applicable.

Also fixed the z-index of the charm browser tab,
so that when the sidebar and the unit details view
appear together in a narrow browser it is still
possible to close the sidebar.

QA:
- deploy the GUI with this branch in a juju-core env:
   juju bootstrap --debug
   juju deploy juju-gui
   juju set juju-gui juju-gui-source=lp:~frankban/juju-gui/inspector-who
- wait for the unit to be ready;
- visit the juju-gui unit detail view;

You should be able to close the sidebar view.
The GUI address links to the right URL (https://).
The ports link to the right URLs without the trailing "/tcp".

R=jeff.pihach
CC=
https://codereview.appspot.com/14439054

https://codereview.appspot.com/14439054/

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

Thanks for the review and QA Jeff!

https://codereview.appspot.com/14439054/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/views/utils.js'
2--- app/views/utils.js 2013-10-10 04:28:39 +0000
3+++ app/views/utils.js 2013-10-10 14:30:29 +0000
4@@ -1365,6 +1365,65 @@
5 var charmUrl = candidate.charm || candidate.get('charm');
6 return utils.isGuiCharmUrl(charmUrl);
7 };
8+ /**
9+ * Normalize the list of open ports on the unit.
10+ * The list of open ports can be received in different formats based on the
11+ * Juju implementations. In essence, while pyJuju sends e.g. [80, 42],
12+ * juju-core includes the protocol information, e.g. ['80/tcp', '47/udp'].
13+ *
14+ * @method normalizeUnitPorts
15+ * @param {Array} ports The list of open ports on a unit.
16+ * @return {Array} A list of objects each one with the following properties:
17+ * - port (int): the port number;
18+ * - protocol (string): 'tcp' or 'udp'.
19+ */
20+ utils.normalizeUnitPorts = function(ports) {
21+ if (!ports) {
22+ return [];
23+ }
24+ return Y.Array.map(ports, function(port) {
25+ var splitted = port.toString().split('/');
26+ return {port: parseInt(splitted[0], 10), protocol: splitted[1] || 'tcp'};
27+ });
28+ };
29+
30+ /**
31+ * Parse a list of normalized open ports.
32+ *
33+ * @method parseUnitPorts
34+ * @param {String} ipAddress The unit IP address.
35+ * @param {Array} normalizedPorts A list of normalized unit ports
36+ * (see utils.normalizeUnitPorts above).
37+ * @return {Array} An array of two elements. The first element is a data
38+ * object describing the IP address. The second element is a list of data
39+ objects describing each open port. A data object is an object with the
40+ following properties:
41+ * - text (string): the text to show in the template;
42+ * - href (string, optional): the URL where the text can link to (
43+ if applicable).
44+ */
45+ utils.parseUnitPorts = function(ipAddress, normalizedPorts) {
46+ var httpHref, httpsHref;
47+ var ipAddressData = {text: ipAddress};
48+ var portDataList = [];
49+ normalizedPorts.forEach(function(normalizedPort) {
50+ var port = normalizedPort.port;
51+ var protocol = normalizedPort.protocol;
52+ var portData = {text: port + '/' + protocol};
53+ if (protocol === 'tcp') {
54+ if (port === 443) {
55+ portData.href = httpsHref = 'https://' + ipAddress + '/';
56+ } else if (port === 80) {
57+ portData.href = httpHref = 'http://' + ipAddress + '/';
58+ } else {
59+ portData.href = 'http://' + ipAddress + ':' + port + '/';
60+ }
61+ }
62+ portDataList.push(portData);
63+ });
64+ ipAddressData.href = httpsHref || httpHref;
65+ return [ipAddressData, portDataList];
66+ };
67
68 Y.Handlebars.registerHelper('unitState', function(relation_errors,
69 agent_state) {
70
71=== modified file 'app/views/viewlets/unit-details.js'
72--- app/views/viewlets/unit-details.js 2013-09-24 20:27:28 +0000
73+++ app/views/viewlets/unit-details.js 2013-10-10 14:30:29 +0000
74@@ -31,50 +31,50 @@
75
76 @method updateAddress
77 @param {Y.Node} node The node to be updated.
78- @param {String} value The IP address.
79- @param {Array} value An array of the ports exposed.
80+ @param {String} ipAddress The IP address.
81+ @param {Array} openPorts An array of the ports exposed.
82 @return {undefined} Mutates the node.
83 */
84- var updateAddress = function(node, value, open_ports) {
85+ var updateAddress = function(node, ipAddress, openPorts) {
86 node.empty();
87- if (!value) { return; }
88- var protocol;
89- var protocols = {443: 'https', 80: 'http'};
90- if (open_ports && open_ports.length) {
91- Object.keys(protocols).some(function(port) {
92- if (open_ports.indexOf(port) >= 0 ||
93- open_ports.indexOf(parseInt(port, 10)) >= 0) {
94- protocol = protocols[port];
95- return true; // Short-circuits the loop.
96- }
97- });
98+ if (!ipAddress) {
99+ return;
100 }
101- var infoNode;
102- if (protocol) {
103- infoNode = Y.Node.create('<a></a>').setAttrs({
104- href: protocol + '://' + value + '/',
105+ var ports = utils.normalizeUnitPorts(openPorts);
106+ var parsed = utils.parseUnitPorts(ipAddress, ports);
107+ var ipAddressData = parsed[0];
108+ var portDataList = parsed[1];
109+ // Render the IP address fragment.
110+ var ipAddressNode;
111+ if (ipAddressData.href) {
112+ ipAddressNode = Y.Node.create('<a></a>').setAttrs({
113+ href: ipAddressData.href,
114 target: '_blank',
115- text: value
116+ text: ipAddressData.text
117 });
118 } else {
119- infoNode = Y.Node.create(value);
120+ ipAddressNode = Y.Node.create(ipAddressData.text);
121 }
122- node.append(infoNode);
123- if (open_ports && open_ports.length) {
124+ node.append(ipAddressNode);
125+ // Render the ports fragment.
126+ if (portDataList.length) {
127 // YUI 3 trims HTML because IE (<10?) does it. :-/
128 node.append('&nbsp;| Ports&nbsp;');
129- var previous;
130- open_ports.forEach(function(port) {
131- if (previous) {
132+ Y.Array.each(portDataList, function(portData, index) {
133+ if (index) {
134 node.append(',&nbsp;');
135 }
136- previous = port;
137- var protocol = protocols[port.toString()] || 'http';
138- node.append(Y.Node.create('<a></a>').setAttrs({
139- href: protocol + '://' + value + ':' + port + '/',
140- target: '_blank',
141- text: port
142- }));
143+ var portNode;
144+ if (portData.href) {
145+ portNode = Y.Node.create('<a></a>').setAttrs({
146+ href: portData.href,
147+ target: '_blank',
148+ text: portData.text
149+ });
150+ } else {
151+ portNode = Y.Node.create(portData.text);
152+ }
153+ node.append(portNode);
154 });
155 }
156 };
157
158=== modified file 'lib/views/browser/browser-icon.less'
159--- lib/views/browser/browser-icon.less 2013-10-01 14:35:09 +0000
160+++ lib/views/browser/browser-icon.less 2013-10-10 14:30:29 +0000
161@@ -10,7 +10,7 @@
162 box-shadow: 3px 0 2px rgba(0, 0, 0, 0.1);
163 border-top-right-radius: 4px;
164 border-bottom-right-radius: 4px;
165- z-index: 600;
166+ z-index: 610;
167 }
168 .content-visible .bws-icon {
169 left: @bws-sidebar-width + @bws-panel-width;
170
171=== modified file 'test/test_unit_detail_viewlet.js'
172--- test/test_unit_detail_viewlet.js 2013-09-27 16:26:18 +0000
173+++ test/test_unit_detail_viewlet.js 2013-10-10 14:30:29 +0000
174@@ -84,18 +84,37 @@
175 var node = Y.Node.create('<span>Delete me</span>');
176 updateAddress(node, '10.0.0.1', [8080]);
177 assert.strictEqual(
178- node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 8080');
179- assert.strictEqual(node.all('a').size(), 1);
180- assert.strictEqual(node.one('a').get('href'), 'http://10.0.0.1:8080/');
181- assert.strictEqual(node.one('a').get('target'), '_blank');
182- assert.strictEqual(node.one('a').get('text'), '8080');
183+ node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 8080/tcp');
184+ assert.strictEqual(node.all('a').size(), 1);
185+ assert.strictEqual(node.one('a').get('href'), 'http://10.0.0.1:8080/');
186+ assert.strictEqual(node.one('a').get('target'), '_blank');
187+ assert.strictEqual(node.one('a').get('text'), '8080/tcp');
188+ });
189+
190+ it('mutates a node with an address and a port/tcp pair', function() {
191+ var node = Y.Node.create('<span>Delete me</span>');
192+ updateAddress(node, '10.0.0.1', ['8080/tcp']);
193+ assert.strictEqual(
194+ node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 8080/tcp');
195+ assert.strictEqual(node.all('a').size(), 1);
196+ assert.strictEqual(node.one('a').get('href'), 'http://10.0.0.1:8080/');
197+ assert.strictEqual(node.one('a').get('target'), '_blank');
198+ assert.strictEqual(node.one('a').get('text'), '8080/tcp');
199+ });
200+
201+ it('mutates a node with an address and a port/udp pair', function() {
202+ var node = Y.Node.create('<span>Delete me</span>');
203+ updateAddress(node, '10.0.0.1', ['8080/udp']);
204+ assert.strictEqual(
205+ node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 8080/udp');
206+ assert.strictEqual(node.all('a').size(), 0);
207 });
208
209 it('mutates a node with an address and http port', function() {
210 var node = Y.Node.create('<span>Delete me</span>');
211 updateAddress(node, '10.0.0.1', [80]);
212 assert.strictEqual(
213- node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 80');
214+ node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 80/tcp');
215 assert.strictEqual(node.all('a').size(), 2);
216 assert.strictEqual(node.one('a').get('href'), 'http://10.0.0.1/');
217 assert.strictEqual(node.one('a').get('target'), '_blank');
218@@ -106,7 +125,7 @@
219 var node = Y.Node.create('<span>Delete me</span>');
220 updateAddress(node, '10.0.0.1', [443]);
221 assert.strictEqual(
222- node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 443');
223+ node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 443/tcp');
224 assert.strictEqual(node.all('a').size(), 2);
225 assert.strictEqual(node.one('a').get('href'), 'https://10.0.0.1/');
226 assert.strictEqual(node.one('a').get('target'), '_blank');
227@@ -117,13 +136,26 @@
228 var node = Y.Node.create('<span>Delete me</span>');
229 updateAddress(node, '10.0.0.1', [443, 8080]);
230 assert.strictEqual(
231- node.get('text').replace(/\s/g, ' '), '10.0.0.1 | Ports 443, 8080');
232+ node.get('text').replace(/\s/g, ' '),
233+ '10.0.0.1 | Ports 443/tcp, 8080/tcp');
234 assert.strictEqual(node.all('a').size(), 3);
235 assert.strictEqual(node.one('a').get('href'), 'https://10.0.0.1/');
236 assert.strictEqual(node.one('a').get('target'), '_blank');
237 assert.strictEqual(node.one('a').get('text'), '10.0.0.1');
238 });
239
240+ it('mutates a node with multiple port/protocol pairs', function() {
241+ var node = Y.Node.create('<span>Delete me</span>');
242+ updateAddress(node, '10.0.0.1', ['42/tcp', '47/udp']);
243+ assert.strictEqual(
244+ node.get('text').replace(/\s/g, ' '),
245+ '10.0.0.1 | Ports 42/tcp, 47/udp');
246+ assert.strictEqual(node.all('a').size(), 1);
247+ assert.strictEqual(node.one('a').get('href'), 'http://10.0.0.1:42/');
248+ assert.strictEqual(node.one('a').get('target'), '_blank');
249+ assert.strictEqual(node.one('a').get('text'), '42/tcp');
250+ });
251+
252 it('instantiates correctly when bound', function() {
253 db.environment.set('annotations', {
254 'landscape-url': 'http://landscape.example.com',
255
256=== modified file 'test/test_utils.js'
257--- test/test_utils.js 2013-10-10 04:28:39 +0000
258+++ test/test_utils.js 2013-10-10 14:30:29 +0000
259@@ -373,6 +373,121 @@
260
261 });
262
263+ describe('normalizeUnitPorts', function() {
264+ var normalizeUnitPorts;
265+
266+ before(function() {
267+ normalizeUnitPorts = utils.normalizeUnitPorts;
268+ });
269+
270+ it('normalizes juju-core ports', function() {
271+ var expected = [
272+ {port: 80, protocol: 'tcp'},
273+ {port: 42, protocol: 'udp'}
274+ ];
275+ var obtained = normalizeUnitPorts(['80/tcp', '42/udp']);
276+ assert.deepEqual(obtained, expected);
277+ });
278+
279+ it('normalizes pyJuju ports', function() {
280+ var expected = [
281+ {port: 80, protocol: 'tcp'},
282+ {port: 443, protocol: 'tcp'},
283+ {port: 8080, protocol: 'tcp'}
284+ ];
285+ var obtained = normalizeUnitPorts([80, '443', 8080]);
286+ assert.deepEqual(obtained, expected);
287+ });
288+
289+ it('returns an empty list if no ports are passed', function() {
290+ assert.deepEqual(normalizeUnitPorts([]), []);
291+ assert.deepEqual(normalizeUnitPorts(undefined), []);
292+ });
293+
294+ });
295+
296+ describe('parseUnitPorts', function() {
297+ var parseUnitPorts;
298+
299+ before(function() {
300+ parseUnitPorts = utils.parseUnitPorts;
301+ });
302+
303+ it('parses generic ports', function() {
304+ var expected = [
305+ {text: '10.0.3.1', href: undefined},
306+ [
307+ {text: '42/tcp', href: 'http://10.0.3.1:42/'},
308+ {text: '47/tcp', href: 'http://10.0.3.1:47/'}
309+ ]
310+ ];
311+ var ports = [{port: 42, protocol: 'tcp'}, {port: 47, protocol: 'tcp'}];
312+ assert.deepEqual(parseUnitPorts('10.0.3.1', ports), expected);
313+ });
314+
315+ it('parses the HTTP port', function() {
316+ var expected = [
317+ {text: '10.0.3.1', href: 'http://10.0.3.1/'},
318+ [
319+ {text: '80/tcp', href: 'http://10.0.3.1/'},
320+ {text: '47/tcp', href: 'http://10.0.3.1:47/'}
321+ ]
322+ ];
323+ var ports = [{port: 80, protocol: 'tcp'}, {port: 47, protocol: 'tcp'}];
324+ assert.deepEqual(parseUnitPorts('10.0.3.1', ports), expected);
325+ });
326+
327+ it('parses the HTTPS port', function() {
328+ var expected = [
329+ {text: '10.0.3.1', href: 'https://10.0.3.1/'},
330+ [
331+ {text: '42/tcp', href: 'http://10.0.3.1:42/'},
332+ {text: '443/tcp', href: 'https://10.0.3.1/'}
333+ ]
334+ ];
335+ var ports = [{port: 42, protocol: 'tcp'}, {port: 443, protocol: 'tcp'}];
336+ assert.deepEqual(parseUnitPorts('10.0.3.1', ports), expected);
337+ });
338+
339+ it('privileges the HTTPS port', function() {
340+ var expected = [
341+ {text: '10.0.3.1', href: 'https://10.0.3.1/'},
342+ [
343+ {text: '42/tcp', href: 'http://10.0.3.1:42/'},
344+ {text: '443/tcp', href: 'https://10.0.3.1/'},
345+ {text: '80/tcp', href: 'http://10.0.3.1/'}
346+ ]
347+ ];
348+ var ports = [
349+ {port: 42, protocol: 'tcp'},
350+ {port: 443, protocol: 'tcp'},
351+ {port: 80, protocol: 'tcp'}
352+ ];
353+ assert.deepEqual(parseUnitPorts('10.0.3.1', ports), expected);
354+ });
355+
356+ it('avoid linking UDP ports', function() {
357+ var expected = [
358+ {text: '10.0.3.1', href: 'https://10.0.3.1/'},
359+ [
360+ {text: '42/udp'},
361+ {text: '443/tcp', href: 'https://10.0.3.1/'}
362+ ]
363+ ];
364+ var ports = [
365+ {port: 42, protocol: 'udp'},
366+ {port: 443, protocol: 'tcp'}
367+ ];
368+ assert.deepEqual(parseUnitPorts('10.0.3.1', ports), expected);
369+ });
370+
371+ it('handles no open ports', function() {
372+ var expected = [{text: '10.0.3.1', href: undefined}, []];
373+ assert.deepEqual(parseUnitPorts('10.0.3.1', []), expected);
374+ });
375+
376+ });
377+
378 });
379
380 (function() {

Subscribers

People subscribed via source and target branches