Merge ~ltrager/maas:2.3_1752754 into maas:2.3

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 2bd80569e4a6e5c9bd25eaaf4f70a68a701bdc5c
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:2.3_1752754
Merge into: maas:2.3
Prerequisite: ~ltrager/maas:2.3_fix_scriptresult_updates_on_trigger
Diff against target: 415 lines (+189/-40)
9 files modified
src/maasserver/static/js/angular/controllers/node_results.js (+21/-10)
src/maasserver/static/js/angular/controllers/tests/test_node_results.js (+46/-0)
src/maasserver/static/js/angular/factories/node_results.js (+10/-1)
src/maasserver/static/js/angular/factories/tests/test_node_results.js (+22/-1)
src/maasserver/static/partials/script-results-list.html (+7/-1)
src/maasserver/websockets/handlers/node_result.py (+21/-12)
src/maasserver/websockets/handlers/tests/test_node_result.py (+32/-11)
src/metadataserver/models/scriptresult.py (+4/-2)
src/metadataserver/models/tests/test_scriptresult.py (+26/-2)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+368114@code.launchpad.net

Commit message

LP: #1752754 - Request historic results over the websocket instead of always sending them.

Backport of 9e35f1a for LP: #1830365

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
~ltrager/maas:2.3_1752754 updated
2bd8056... by Lee Trager

Merge branch '2.3' into 2.3_1752754

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/static/js/angular/controllers/node_results.js b/src/maasserver/static/js/angular/controllers/node_results.js
2index 101e500..59f3f6f 100644
3--- a/src/maasserver/static/js/angular/controllers/node_results.js
4+++ b/src/maasserver/static/js/angular/controllers/node_results.js
5@@ -1,4 +1,4 @@
6-/* Copyright 2017 Canonical Ltd. This software is licensed under the
7+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
8 * GNU Affero General Public License version 3 (see the file LICENSE).
9 *
10 * MAAS Node Results Controller
11@@ -135,17 +135,12 @@ angular.module('MAAS').controller('NodeResultsController', [
12 });
13 } else {
14 var result = null;
15- var i, j;
16+ var i;
17 // Find the installation result to be displayed.
18 for(i = 0; i < $scope.installation_results.length; i++) {
19- var hlist = $scope.installation_results[i].history_list;
20- for(j = 0; j < hlist.length; j++) {
21- if(hlist[j].id === $scope.logs.option.id) {
22- result = hlist[j];
23- break;
24- }
25- }
26- if(result) {
27+ if($scope.installation_results[i].id ===
28+ $scope.logs.option.id) {
29+ result = $scope.installation_results[i];
30 break;
31 }
32 }
33@@ -195,6 +190,22 @@ angular.module('MAAS').controller('NodeResultsController', [
34 }
35 };
36
37+ $scope.loadHistory = function(result) {
38+ result.showing_results = false;
39+ // History has already been loaded, no need to request it.
40+ if(angular.isArray(result.history_list)) {
41+ result.showing_history = true;
42+ return;
43+ }
44+ result.loading_history = true;
45+ $scope.nodeResultsManager.get_history(result.id).then(
46+ function(history) {
47+ result.history_list = history;
48+ result.loading_history = false;
49+ result.showing_history = true;
50+ });
51+ };
52+
53 // Destroy the NodeResultsManager when the scope is destroyed. This is
54 // so the client will not recieve any more notifications about results
55 // from this node.
56diff --git a/src/maasserver/static/js/angular/controllers/tests/test_node_results.js b/src/maasserver/static/js/angular/controllers/tests/test_node_results.js
57index f2f44d7..fbeb5a1 100644
58--- a/src/maasserver/static/js/angular/controllers/tests/test_node_results.js
59+++ b/src/maasserver/static/js/angular/controllers/tests/test_node_results.js
60@@ -433,4 +433,50 @@ describe("NodeResultsController", function() {
61 "BUG: Unknown log status " + installation_result.status);
62 });
63 });
64+
65+ describe("loadHistory", function() {
66+ it("loads results", function() {
67+ var defer = $q.defer();
68+ var controller = makeController();
69+ var result = {
70+ id: makeInteger(0, 100)
71+ };
72+ var history_list = [
73+ {id: makeInteger(0, 100)},
74+ {id: makeInteger(0, 100)},
75+ {id: makeInteger(0, 100)}
76+ ];
77+ $scope.node = node;
78+ $scope.nodeResultsManager = NodeResultsManagerFactory.getManager(
79+ $scope.node);
80+ spyOn($scope.nodeResultsManager, "get_history").and.returnValue(
81+ defer.promise);
82+
83+ $scope.loadHistory(result);
84+ defer.resolve(history_list);
85+ $rootScope.$digest();
86+
87+ expect(result.history_list).toBe(history_list);
88+ expect(result.loading_history).toBe(false);
89+ expect(result.showing_history).toBe(true);
90+ });
91+
92+ it("doesnt reload", function() {
93+ var controller = makeController();
94+ var result = {
95+ id: makeInteger(0, 100),
96+ history_list: [{id: makeInteger(0, 100)}]
97+ };
98+ $scope.node = node;
99+ $scope.nodeResultsManager = NodeResultsManagerFactory.getManager(
100+ $scope.node);
101+ spyOn($scope.nodeResultsManager, "get_history");
102+
103+ $scope.loadHistory(result);
104+
105+ expect(result.showing_history).toBe(true);
106+ expect(
107+ $scope.nodeResultsManager.get_history).not.toHaveBeenCalled();
108+ });
109+ });
110 });
111diff --git a/src/maasserver/static/js/angular/factories/node_results.js b/src/maasserver/static/js/angular/factories/node_results.js
112index 1a0a5de..94a52f7 100644
113--- a/src/maasserver/static/js/angular/factories/node_results.js
114+++ b/src/maasserver/static/js/angular/factories/node_results.js
115@@ -1,4 +1,4 @@
116-/* Copyright 2017 Canonical Ltd. This software is licensed under the
117+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
118 * GNU Affero General Public License version 3 (see the file LICENSE).
119 *
120 * MAAS NodeResultsManager Manager
121@@ -266,6 +266,15 @@ angular.module('MAAS').factory(
122 return RegionConnection.callMethod(method, params);
123 };
124
125+ // Get historic data.
126+ NodeResultsManager.prototype.get_history = function(script_id) {
127+ var method = this._handler + ".get_history";
128+ var params = {
129+ id: script_id,
130+ };
131+ return RegionConnection.callMethod(method, params);
132+ };
133+
134 // Factory that holds all created NodeResultsManagers.
135 function NodeResultsManagerFactory() {
136 // Holds a list of all NodeResultsManagers that have been created.
137diff --git a/src/maasserver/static/js/angular/factories/tests/test_node_results.js b/src/maasserver/static/js/angular/factories/tests/test_node_results.js
138index 859dda3..2508969 100644
139--- a/src/maasserver/static/js/angular/factories/tests/test_node_results.js
140+++ b/src/maasserver/static/js/angular/factories/tests/test_node_results.js
141@@ -1,4 +1,4 @@
142-/* Copyright 2017 Canonical Ltd. This software is licensed under the
143+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
144 * GNU Affero General Public License version 3 (see the file LICENSE).
145 *
146 * Unit tests for NodeResultsManagerFactory.
147@@ -532,6 +532,27 @@ describe("NodeResultsManagerFactory", function() {
148 });
149 });
150
151+ describe("get_history", function() {
152+
153+ it("calls NodeResultHandler.get_history", function(done) {
154+ var node = makenode();
155+ var output = [{
156+ id: makeInteger(0, 100),
157+ name: makeName("output"),
158+ status: makeInteger(0, 100)
159+ }];
160+ var id = makeInteger(0, 100);
161+ webSocket.returnData.push(makeFakeResponse(output));
162+ NodeResultsManager = NodeResultsManagerFactory.getManager(node);
163+ NodeResultsManager.get_history(id).then(function() {
164+ var sentObject = angular.fromJson(webSocket.sentData[0]);
165+ expect(sentObject.method).toBe("noderesult.get_history");
166+ expect(sentObject.params.id).toEqual(id);
167+ done();
168+ });
169+ });
170+ });
171+
172 describe("destroyManager", function() {
173
174 it("removes manager from _managers", function() {
175diff --git a/src/maasserver/static/partials/script-results-list.html b/src/maasserver/static/partials/script-results-list.html
176index 913978c..67ca3ab 100644
177--- a/src/maasserver/static/partials/script-results-list.html
178+++ b/src/maasserver/static/partials/script-results-list.html
179@@ -44,7 +44,7 @@
180 <div class="table__controls-menu ng-hide" role="menu" data-ng-show="isToggled">
181 <button class="table__controls-action" aria-label="View metrics" data-ng-if="!result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = true">View metrics</button>
182 <button class="table__controls-action" aria-label="Hide metrics" data-ng-if="result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = false">Hide metrics</button>
183- <button class="table__controls-action" aria-label="View previous {$ result.result_section $}" data-ng-if="!result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = true">View previous {$ result.result_section $}</button>
184+ <button class="table__controls-action" aria-label="View previous {$ result.result_section $}" data-ng-if="!result.showing_history" data-ng-click="toggleMenu(); loadHistory(result)">View previous {$ result.result_section $}</button>
185 <button class="table__controls-action" aria-label="Hide previous {$ result.result_section $}" data-ng-if="result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = false">Hide previous {$ result.result_section $}</button>
186 </div>
187 </div>
188@@ -60,6 +60,11 @@
189 </div>
190 </div>
191
192+ <div class="p-table-expanding__panel col-12" aria-label="loading history" data-ng-if="result.loading_history">
193+ <div class="col-12">
194+ <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>
195+ </div>
196+ </div>
197 <div class="table__dropdown" aria-label="history" data-ng-if="result.showing_history">
198 <div class="table__row is-active">
199 <div class="table__data table-col--100">
200@@ -83,6 +88,7 @@
201 <p class="u-align--center u-margin--bottom">
202 <button class="button--secondary button--inline" data-ng-click="result.showing_history = false">Hide previous {$ result.result_section $}</button>
203 </p>
204+
205 </div>
206 </div>
207 </div>
208diff --git a/src/maasserver/websockets/handlers/node_result.py b/src/maasserver/websockets/handlers/node_result.py
209index e6e516e..cf04e58 100644
210--- a/src/maasserver/websockets/handlers/node_result.py
211+++ b/src/maasserver/websockets/handlers/node_result.py
212@@ -32,6 +32,7 @@ class NodeResultHandler(TimestampedModelHandler):
213 'clear',
214 'get',
215 'get_result_data',
216+ 'get_history',
217 'list',
218 ]
219 listen_channels = ['scriptresult']
220@@ -88,18 +89,6 @@ class NodeResultHandler(TimestampedModelHandler):
221 # Script object.
222 data["hardware_type"] = HARDWARE_TYPE.NODE
223 data["tags"] = 'commissioning'
224- data["history_list"] = [
225- {
226- "id": history.id,
227- "updated": dehydrate_datetime(history.updated),
228- "status": history.status,
229- "status_name": history.status_name,
230- "runtime": history.runtime,
231- "starttime": history.starttime,
232- "endtime": history.endtime,
233- "estimated_runtime": history.estimated_runtime,
234- } for history in obj.history
235- ]
236 try:
237 results = obj.read_results()
238 except ValidationError as e:
239@@ -213,6 +202,26 @@ class NodeResultHandler(TimestampedModelHandler):
240 else:
241 return "Unknown data_type %s" % data_type
242
243+ def get_history(self, params):
244+ """Return a list of historic results."""
245+ id = params.get('id')
246+ script_result = ScriptResult.objects.filter(id=id).only(
247+ 'script_id', 'script_set_id', 'physical_blockdevice_id',
248+ 'script_name').first()
249+ history_qs = script_result.history.only(
250+ 'id', 'updated', 'status', 'started', 'ended', 'script_id',
251+ 'script_name', 'script_set_id', 'physical_blockdevice_id')
252+ return [{
253+ 'id': history.id,
254+ 'updated': dehydrate_datetime(history.updated),
255+ 'status': history.status,
256+ 'status_name': history.status_name,
257+ 'runtime': history.runtime,
258+ 'starttime': history.starttime,
259+ 'endtime': history.endtime,
260+ 'estimated_runtime': history.estimated_runtime,
261+ } for history in history_qs]
262+
263 def clear(self, params):
264 """Clears the current node for events.
265
266diff --git a/src/maasserver/websockets/handlers/tests/test_node_result.py b/src/maasserver/websockets/handlers/tests/test_node_result.py
267index 5b3fc37..a8fe7fe 100644
268--- a/src/maasserver/websockets/handlers/tests/test_node_result.py
269+++ b/src/maasserver/websockets/handlers/tests/test_node_result.py
270@@ -1,4 +1,4 @@
271-# Copyright 2017 Canonical Ltd. This software is licensed under the
272+# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
273 # GNU Affero General Public License version 3 (see the file LICENSE).
274
275 """Tests for `maasserver.websockets.handlers.node_result`"""
276@@ -16,6 +16,7 @@ from maasserver.websockets.base import (
277 HandlerPKError,
278 )
279 from maasserver.websockets.handlers.node_result import NodeResultHandler
280+from maastesting.djangotestcase import CountQueries
281 from metadataserver.enum import (
282 HARDWARE_TYPE,
283 HARDWARE_TYPE_CHOICES,
284@@ -49,16 +50,6 @@ class TestNodeResultHandler(MAASServerTestCase):
285 "result_type": script_result.script_set.result_type,
286 "hardware_type": script_result.script.hardware_type,
287 "tags": ", ".join(script_result.script.tags),
288- "history_list": [{
289- "id": history.id,
290- "updated": dehydrate_datetime(history.updated),
291- "status": history.status,
292- "status_name": history.status_name,
293- "runtime": history.runtime,
294- "starttime": history.starttime,
295- "endtime": history.endtime,
296- "estimated_runtime": history.estimated_runtime,
297- } for history in script_result.history],
298 "results": [{
299 "name": key,
300 "title": key,
301@@ -333,6 +324,36 @@ class TestNodeResultHandler(MAASServerTestCase):
302 'data_type': unknown_data_type,
303 }))
304
305+ def test_get_history(self):
306+ user = factory.make_User()
307+ handler = NodeResultHandler(user, {})
308+ node = factory.make_Node(owner=user)
309+ script = factory.make_Script()
310+ script_results = []
311+ for _ in range(10):
312+ script_set = factory.make_ScriptSet(node=node)
313+ script_results.append(factory.make_ScriptResult(
314+ script=script, script_set=script_set,
315+ status=SCRIPT_STATUS.PASSED))
316+ latest_script_result = script_results[-1]
317+ script_results = sorted(
318+ script_results, key=lambda i: i.id, reverse=True)
319+ queries = CountQueries()
320+ with queries:
321+ ret = handler.get_history({'id': latest_script_result.id})
322+ self.assertEqual(4, queries.num_queries)
323+ for script_result, out in zip(script_results, ret):
324+ self.assertDictEqual({
325+ 'id': script_result.id,
326+ 'updated': dehydrate_datetime(script_result.updated),
327+ 'status': script_result.status,
328+ 'status_name': script_result.status_name,
329+ 'runtime': script_result.runtime,
330+ 'starttime': script_result.starttime,
331+ 'endtime': script_result.endtime,
332+ 'estimated_runtime': script_result.estimated_runtime,
333+ }, out)
334+
335 def test_clear_removes_system_id_from_cache(self):
336 user = factory.make_User()
337 handler = NodeResultHandler(user, {})
338diff --git a/src/metadataserver/models/scriptresult.py b/src/metadataserver/models/scriptresult.py
339index 741c6db..7f0ea1b 100644
340--- a/src/metadataserver/models/scriptresult.py
341+++ b/src/metadataserver/models/scriptresult.py
342@@ -1,4 +1,4 @@
343-# Copyright 2017 Canonical Ltd. This software is licensed under the
344+# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
345 # GNU Affero General Public License version 3 (see the file LICENSE).
346 __all__ = [
347 'ScriptResult',
348@@ -139,7 +139,9 @@ class ScriptResult(CleanSave, TimestampedModel):
349 return self.runtime
350 runtime = None
351 # Get an estimated runtime from previous runs.
352- for script_result in self.history:
353+ for script_result in self.history.only(
354+ 'status', 'started', 'ended', 'script_id', 'script_name',
355+ 'script_set_id', 'physical_blockdevice_id'):
356 # Only look at passed results when calculating an estimated
357 # runtime. Failed results may take longer or shorter than
358 # average. Don't use self.history.filter for this as the now
359diff --git a/src/metadataserver/models/tests/test_scriptresult.py b/src/metadataserver/models/tests/test_scriptresult.py
360index 11269d9..5930fae 100644
361--- a/src/metadataserver/models/tests/test_scriptresult.py
362+++ b/src/metadataserver/models/tests/test_scriptresult.py
363@@ -1,4 +1,4 @@
364-# Copyright 2017 Canonical Ltd. This software is licensed under the
365+# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
366 # GNU Affero General Public License version 3 (see the file LICENSE).
367
368 __all__ = []
369@@ -19,6 +19,7 @@ from maasserver.models import (
370 from maasserver.testing.factory import factory
371 from maasserver.testing.testcase import MAASServerTestCase
372 from maasserver.utils.orm import reload_object
373+from maastesting.djangotestcase import CountQueries
374 from maastesting.matchers import (
375 DocTestMatches,
376 MockCalledOnceWith,
377@@ -28,7 +29,10 @@ from metadataserver.enum import (
378 SCRIPT_STATUS,
379 SCRIPT_STATUS_CHOICES,
380 )
381-from metadataserver.models import scriptresult as scriptresult_module
382+from metadataserver.models import (
383+ ScriptResult,
384+ scriptresult as scriptresult_module,
385+)
386 from provisioningserver.events import EVENT_TYPES
387 import yaml
388
389@@ -624,6 +628,26 @@ class TestScriptResult(MAASServerTestCase):
390 script_result = script_results[-1]
391 self.assertItemsEqual(script_results, script_result.history)
392
393+ def test_history_query_count(self):
394+ script_name = factory.make_name('script_name')
395+ script_set = factory.make_ScriptSet()
396+ script_results = [
397+ factory.make_ScriptResult(
398+ script_name=script_name, script_set=script_set)
399+ for _ in range(10)
400+ ]
401+ queries_one = CountQueries()
402+ script_result_one = ScriptResult.objects.get(id=script_results[0].id)
403+ with queries_one:
404+ script_result_one.history
405+ queries_many = CountQueries()
406+ script_result_many = ScriptResult.objects.get(
407+ id=script_results[-1].id)
408+ with queries_many:
409+ script_result_many.history
410+ self.assertEquals(1, queries_one.num_queries)
411+ self.assertEquals(1, queries_many.num_queries)
412+
413 def test_history_storage_device(self):
414 # Regression test for LP: #1721524
415 script = factory.make_Script()

Subscribers

People subscribed via source and target branches