Merge lp:~blake-rouse/maas/fix-1462019 into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 3991
Proposed branch: lp:~blake-rouse/maas/fix-1462019
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 193 lines (+74/-18)
2 files modified
src/maasserver/static/js/angular/controllers/node_details.js (+35/-18)
src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+39/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1462019
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+261149@code.launchpad.net

Commit message

Add some defensive programming in the NodeDetailsController preventing the UI from becoming unresponsive when things go crazy.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good. Might deserve a test or two.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Thanks for the review. I have added tests!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/static/js/angular/controllers/node_details.js'
2--- src/maasserver/static/js/angular/controllers/node_details.js 2015-06-03 20:22:58 +0000
3+++ src/maasserver/static/js/angular/controllers/node_details.js 2015-06-05 13:16:02 +0000
4@@ -155,12 +155,14 @@
5
6 // Show the cluster disconnected error if the cluster is not
7 // connected.
8- var cluster = ClustersManager.getItemFromList(
9- $scope.node.nodegroup.id);
10- if(!cluster.connected) {
11- showError("cluster_disconnected");
12- } else {
13- hideError("cluster_disconnected");
14+ if(angular.isObject($scope.node.nodegroup)) {
15+ var cluster = ClustersManager.getItemFromList(
16+ $scope.node.nodegroup.id);
17+ if(!cluster.connected) {
18+ showError("cluster_disconnected");
19+ } else {
20+ hideError("cluster_disconnected");
21+ }
22 }
23 }
24
25@@ -206,18 +208,21 @@
26
27 // Always keep the available power types up-to-date even in
28 // editing mode.
29- var cluster = ClustersManager.getItemFromList(
30- $scope.node.nodegroup.id);
31+ var cluster;
32+ if(angular.isObject($scope.node.nodegroup)) {
33+ cluster = ClustersManager.getItemFromList(
34+ $scope.node.nodegroup.id);
35+ }
36 if(angular.isObject(cluster)) {
37 $scope.power.types = cluster.power_types;
38 } else {
39 $scope.power.types = [];
40 }
41
42- // If the cluster is disconnected then always force editing to
43- // false. Its not possible to stay editing the power section
44- // when the cluster is disconnected.
45- if(!cluster.connected) {
46+ // If the no cluster or the cluster is disconnected then always
47+ // force editing to false. Its not possible to stay editing the
48+ // power section when the cluster is disconnected.
49+ if(!angular.isObject(cluster) || !cluster.connected) {
50 $scope.power.editing = false;
51 }
52
53@@ -261,8 +266,10 @@
54 return;
55 }
56
57- $scope.summary.cluster.selected = ClustersManager.getItemFromList(
58- $scope.node.nodegroup.id);
59+ if(angular.isObject($scope.node.nodegroup)) {
60+ $scope.summary.cluster.selected =
61+ ClustersManager.getItemFromList($scope.node.nodegroup.id);
62+ }
63 $scope.summary.zone.selected = ZonesManager.getItemFromList(
64 $scope.node.zone.id);
65 $scope.summary.architecture.selected = $scope.node.architecture;
66@@ -311,8 +318,10 @@
67 $scope.machine_output.viewable = (
68 angular.isString($scope.node.summary_xml) ||
69 angular.isString($scope.node.summary_yaml) ||
70- $scope.node.commissioning_results.length > 0 ||
71- $scope.node.installation_results.length > 0);
72+ (angular.isArray($scope.node.commissioning_results) &&
73+ $scope.node.commissioning_results.length > 0) ||
74+ (angular.isArray($scope.node.installation_results) &&
75+ $scope.node.installation_results.length > 0));
76
77 // Grab the selected view name, so it can be kept the same if
78 // possible.
79@@ -336,13 +345,15 @@
80 title: "Commissioning Summary"
81 });
82 }
83- if($scope.node.commissioning_results.length > 0) {
84+ if(angular.isArray($scope.node.commissioning_results) &&
85+ $scope.node.commissioning_results.length > 0) {
86 $scope.machine_output.views.push({
87 name: "output",
88 title: "Commissioning Output"
89 });
90 }
91- if($scope.node.installation_results.length > 0) {
92+ if(angular.isArray($scope.node.installation_results) &&
93+ $scope.node.installation_results.length > 0) {
94 $scope.machine_output.views.push({
95 name: "install",
96 title: "Installation Output"
97@@ -923,6 +934,9 @@
98 if(!angular.isObject($scope.node)) {
99 return false;
100 }
101+ if(!angular.isArray($scope.node.events)) {
102+ return false;
103+ }
104 return (
105 $scope.node.events.length > 0 &&
106 $scope.node.events.length > $scope.events.limit &&
107@@ -980,6 +994,9 @@
108 // results, but it is unused. Only one installation result will
109 // exists for a node. Grab the first one in the array.
110 var results = $scope.node.installation_results;
111+ if(!angular.isArray(results)) {
112+ return "";
113+ }
114 if(results.length === 0) {
115 return "";
116 } else {
117
118=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details.js'
119--- src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2015-06-03 21:21:47 +0000
120+++ src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2015-06-05 13:16:02 +0000
121@@ -347,6 +347,16 @@
122 expect($scope.summary.editing).toBe(false);
123 });
124
125+ it("skips cluster_disconnected error if the nodegroup on node is invalid",
126+ function() {
127+ var cluster = ClustersManager.getItemFromList(node.nodegroup.id);
128+ cluster.connected = false;
129+ node.nodegroup = undefined;
130+
131+ var controller = makeControllerResolveSetActiveItem();
132+ expect($scope.errors.cluster_disconnected.viewable).toBe(false);
133+ });
134+
135 it("cluster_disconnected error visible if cluster disconnected",
136 function() {
137 var cluster = ClustersManager.getItemFromList(node.nodegroup.id);
138@@ -509,12 +519,26 @@
139 expect($scope.machine_output.viewable).toBe(true);
140 });
141
142+ it("machine output not visible if commissioning_results not an array",
143+ function() {
144+ node.commissioning_results = undefined;
145+ var controller = makeControllerResolveSetActiveItem();
146+ expect($scope.machine_output.viewable).toBe(false);
147+ });
148+
149 it("machine output visible if installation_results", function() {
150 node.installation_results.push({});
151 var controller = makeControllerResolveSetActiveItem();
152 expect($scope.machine_output.viewable).toBe(true);
153 });
154
155+ it("machine output not visible if installation_results not an array",
156+ function() {
157+ node.installation_results = undefined;
158+ var controller = makeControllerResolveSetActiveItem();
159+ expect($scope.machine_output.viewable).toBe(false);
160+ });
161+
162 it("machine output summary view available if summary_xml and summary_yaml",
163 function() {
164 node.summary_xml = node.summary_yaml = "summary";
165@@ -2035,6 +2059,13 @@
166 expect($scope.allowShowMoreEvents()).toBe(false);
167 });
168
169+ it("returns false if node.events is not array", function() {
170+ var controller = makeController();
171+ $scope.node = node;
172+ $scope.node.events = undefined;
173+ expect($scope.allowShowMoreEvents()).toBe(false);
174+ });
175+
176 it("returns false if node has no events", function() {
177 var controller = makeController();
178 $scope.node = node;
179@@ -2165,6 +2196,14 @@
180 expect($scope.getInstallationData()).toBe("");
181 });
182
183+ it("returns blank string if installation results not an array",
184+ function() {
185+ var controller = makeController();
186+ $scope.node = makeNode();
187+ $scope.node.installation_results = undefined;
188+ expect($scope.getInstallationData()).toBe("");
189+ });
190+
191 it("returns blank string if no installation results", function() {
192 var controller = makeController();
193 $scope.node = makeNode();