Merge lp:~blake-rouse/maas/fix-1443959 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: 3986
Proposed branch: lp:~blake-rouse/maas/fix-1443959
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 289 lines (+112/-30)
4 files modified
src/maasserver/static/js/angular/controllers/nodes_list.js (+20/-4)
src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js (+35/-4)
src/maasserver/static/js/angular/services/search.js (+32/-20)
src/maasserver/static/js/angular/services/tests/test_search.js (+25/-2)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1443959
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+261026@code.launchpad.net

Commit message

Save search filters for nodes and devices when changing the current view in angular.

This will keep the current search when going back and forth between the node listing and node details view. Now if you refresh the page the filters will reset.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks generally good. I have a minor comment below about some code that looks to bit a bit boilerplate. I think we might be able to do better with some slight refactoring; right now it seems like we have objects and constants scattered about for the nodes and devices; maybe if a single object knew about all these, it would be cleaner?

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

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/nodes_list.js'
2--- src/maasserver/static/js/angular/controllers/nodes_list.js 2015-05-26 13:52:53 +0000
3+++ src/maasserver/static/js/angular/controllers/nodes_list.js 2015-06-03 20:11:57 +0000
4@@ -45,7 +45,7 @@
5 $scope.tabs.nodes.predicate = 'fqdn';
6 $scope.tabs.nodes.allViewableChecked = false;
7 $scope.tabs.nodes.metadata = NodesManager.getMetadata();
8- $scope.tabs.nodes.filters = SearchService.emptyFilter;
9+ $scope.tabs.nodes.filters = SearchService.getEmptyFilter();
10 $scope.tabs.nodes.column = 'fqdn';
11 $scope.tabs.nodes.actionOption = null;
12 $scope.tabs.nodes.takeActionOptions = GeneralManager.getData(
13@@ -75,7 +75,7 @@
14 $scope.tabs.devices.predicate = 'fqdn';
15 $scope.tabs.devices.allViewableChecked = false;
16 $scope.tabs.devices.metadata = DevicesManager.getMetadata();
17- $scope.tabs.devices.filters = SearchService.emptyFilter;
18+ $scope.tabs.devices.filters = SearchService.getEmptyFilter();
19 $scope.tabs.devices.column = 'fqdn';
20 $scope.tabs.devices.actionOption = null;
21 $scope.tabs.devices.takeActionOptions = GeneralManager.getData(
22@@ -289,7 +289,7 @@
23 var filters = SearchService.getCurrentFilters(
24 $scope.tabs[tab].search);
25 if(filters === null) {
26- $scope.tabs[tab].filters = SearchService.emptyFilter;
27+ $scope.tabs[tab].filters = SearchService.getEmptyFilter();
28 $scope.tabs[tab].searchValid = false;
29 } else {
30 $scope.tabs[tab].filters = filters;
31@@ -477,11 +477,27 @@
32 $scope.loading = false;
33 });
34
35- // Stop polling when the scope is destroyed.
36+ // Stop polling and save the current filter when the scope is destroyed.
37 $scope.$on("$destroy", function() {
38 GeneralManager.stopPolling("osinfo");
39+ SearchService.storeFilters("nodes", $scope.tabs.nodes.filters);
40+ SearchService.storeFilters("devices", $scope.tabs.devices.filters);
41 });
42
43+ // Restore the filters if any saved.
44+ var nodesFilter = SearchService.retrieveFilters("nodes");
45+ if(angular.isObject(nodesFilter)) {
46+ $scope.tabs.nodes.search = SearchService.filtersToString(
47+ nodesFilter);
48+ $scope.updateFilters("nodes");
49+ }
50+ var devicesFilter = SearchService.retrieveFilters("devices");
51+ if(angular.isObject(devicesFilter)) {
52+ $scope.tabs.devices.search = SearchService.filtersToString(
53+ devicesFilter);
54+ $scope.updateFilters("devices");
55+ }
56+
57 // Switch to the specified tab, if specified.
58 if($routeParams.tab === "nodes" || $routeParams.tab === "devices") {
59 $scope.toggleTab($routeParams.tab);
60
61=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js'
62--- src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js 2015-05-26 13:52:53 +0000
63+++ src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js 2015-06-03 20:11:57 +0000
64@@ -149,6 +149,18 @@
65 "osinfo");
66 });
67
68+ it("saves current filters for nodes and devices when scope destroyed",
69+ function() {
70+ var controller = makeController();
71+ var nodesFilters = {}, devicesFilters = {};
72+ $scope.tabs.nodes.filters = nodesFilters;
73+ $scope.tabs.devices.filters = devicesFilters;
74+ $scope.$destroy();
75+ expect(SearchService.retrieveFilters("nodes")).toBe(nodesFilters);
76+ expect(SearchService.retrieveFilters("devices")).toBe(
77+ devicesFilters);
78+ });
79+
80 it("calls loadManagers with NodesManager, DevicesManager," +
81 "GeneralManager, UsersManager",
82 function() {
83@@ -166,6 +178,24 @@
84 expect($scope.loading).toBe(false);
85 });
86
87+ it("sets nodes search from SearchService",
88+ function() {
89+ var query = makeName("query");
90+ SearchService.storeFilters(
91+ "nodes", SearchService.getCurrentFilters(query));
92+ var controller = makeController();
93+ expect($scope.tabs.nodes.search).toBe(query);
94+ });
95+
96+ it("sets devices search from SearchService",
97+ function() {
98+ var query = makeName("query");
99+ SearchService.storeFilters(
100+ "devices", SearchService.getCurrentFilters(query));
101+ var controller = makeController();
102+ expect($scope.tabs.devices.search).toBe(query);
103+ });
104+
105 it("sets nodes search from $routeParams.query",
106 function() {
107 var query = makeName("query");
108@@ -228,7 +258,8 @@
109 expect(tabScope.selectedItems).toBe(
110 tabScope.manager.getSelectedItems());
111 expect(tabScope.metadata).toBe(tabScope.manager.getMetadata());
112- expect(tabScope.filters).toBe(SearchService.emptyFilter);
113+ expect(tabScope.filters).toEqual(
114+ SearchService.getEmptyFilter());
115 expect(tabScope.column).toBe("fqdn");
116 expect(tabScope.actionOption).toBeNull();
117 expect(tabScope.takeActionOptions).toEqual([]);
118@@ -440,7 +471,7 @@
119 it("calls SearchService.toggleFilter", function() {
120 var controller = makeController();
121 spyOn(SearchService, "toggleFilter").and.returnValue(
122- SearchService.emptyFilter);
123+ SearchService.getEmptyFilter());
124 $scope.toggleFilter("hostname", "test", tab);
125 expect(SearchService.toggleFilter).toHaveBeenCalled();
126 });
127@@ -507,8 +538,8 @@
128 $scope.tabs[tab].search = "test hostname:(name";
129 $scope.updateFilters(tab);
130 expect(
131- $scope.tabs[tab].filters).toBe(
132- SearchService.emptyFilter);
133+ $scope.tabs[tab].filters).toEqual(
134+ SearchService.getEmptyFilter());
135 expect($scope.tabs[tab].searchValid).toBe(false);
136 });
137 });
138
139=== modified file 'src/maasserver/static/js/angular/services/search.js'
140--- src/maasserver/static/js/angular/services/search.js 2015-01-29 21:35:52 +0000
141+++ src/maasserver/static/js/angular/services/search.js 2015-06-03 20:11:57 +0000
142@@ -6,13 +6,21 @@
143
144 angular.module('MAAS').service('SearchService', function() {
145
146+ // Holds an empty filter object.
147+ var emptyFilter = { _: [] };
148+
149+ // Return a new empty filter;
150+ this.getEmptyFilter = function() {
151+ return angular.copy(emptyFilter);
152+ };
153+
154 // Splits the search string into different terms based on white space.
155 // This handles the ability for whitespace to be inside of '(', ')'.
156 //
157 // XXX blake_r 28-01-15: This could be improved with a regex, but was
158 // unable to come up with one that would allow me to validate the end
159 // ')' in the string.
160- function getSplitSearch(search) {
161+ this.getSplitSearch = function(search) {
162 var terms = search.split(' ');
163 var fixedTerms = [];
164 var spanningParentheses = false;
165@@ -46,15 +54,15 @@
166 return null;
167 }
168 return fixedTerms;
169- }
170+ };
171
172 // Return all of the currently active filters for the given search.
173- function getCurrentFilters(search) {
174- var filters = { _: [] };
175+ this.getCurrentFilters = function(search) {
176+ var filters = this.getEmptyFilter();
177 if(search.length === 0) {
178 return filters;
179 }
180- var searchTerms = getSplitSearch(search);
181+ var searchTerms = this.getSplitSearch(search);
182 if(!searchTerms) {
183 return null;
184 }
185@@ -94,10 +102,10 @@
186 }
187 });
188 return filters;
189- }
190+ };
191
192 // Convert "filters" into a search string.
193- function filtersToString(filters) {
194+ this.filtersToString = function(filters) {
195 var search = "";
196 angular.forEach(filters, function(terms, type) {
197 // Skip empty and skip "_" as it gets appended at the
198@@ -111,19 +119,19 @@
199 search = filters._.join(" ") + " " + search;
200 }
201 return search.trim();
202- }
203+ };
204
205 // Return true if the type and value are in the filters.
206- function isFilterActive(filters, type, value) {
207+ this.isFilterActive = function(filters, type, value) {
208 var values = filters[type];
209 if(angular.isUndefined(values)) {
210 return false;
211 }
212 return values.indexOf(value) !== -1;
213- }
214+ };
215
216 // Toggles a filter on or off based on type and value.
217- function toggleFilter(filters, type, value) {
218+ this.toggleFilter = function(filters, type, value) {
219 if(angular.isUndefined(filters[type])) {
220 filters[type] = [];
221 }
222@@ -134,14 +142,18 @@
223 filters[type].splice(idx, 1);
224 }
225 return filters;
226- }
227-
228- return {
229- emptyFilter: { _: [] },
230- getSplitSearch: getSplitSearch,
231- getCurrentFilters: getCurrentFilters,
232- filtersToString: filtersToString,
233- isFilterActive: isFilterActive,
234- toggleFilter: toggleFilter
235+ };
236+
237+ // Holds all stored filters.
238+ var storedFilters = {};
239+
240+ // Store a filter for later.
241+ this.storeFilters = function(name, filters) {
242+ storedFilters[name] = filters;
243+ };
244+
245+ // Retrieve a stored fitler.
246+ this.retrieveFilters = function(name) {
247+ return storedFilters[name];
248 };
249 });
250
251=== modified file 'src/maasserver/static/js/angular/services/tests/test_search.js'
252--- src/maasserver/static/js/angular/services/tests/test_search.js 2015-01-29 21:35:52 +0000
253+++ src/maasserver/static/js/angular/services/tests/test_search.js 2015-06-03 20:11:57 +0000
254@@ -146,10 +146,33 @@
255 });
256 });
257
258- describe("emptyFilter", function() {
259+ describe("getEmptyFilter", function() {
260
261 it("includes _ empty list", function() {
262- expect(SearchService.emptyFilter).toEqual({ _: [] });
263+ expect(SearchService.getEmptyFilter()).toEqual({ _: [] });
264+ });
265+
266+ it("returns different object on each call", function() {
267+ var one = SearchService.getEmptyFilter();
268+ var two = SearchService.getEmptyFilter();
269+ expect(one).not.toBe(two);
270+ });
271+ });
272+
273+ describe("storeFilters/retrieveFilters", function() {
274+
275+ it("stores and retrieves the same object", function() {
276+ var i, names = [], objects = [];
277+ for(i = 0; i < 3; i++) {
278+ names.push(makeName("name"));
279+ objects.push({});
280+ }
281+ angular.forEach(names, function(name, idx) {
282+ SearchService.storeFilters(name, objects[idx]);
283+ });
284+ angular.forEach(names, function(name, idx) {
285+ expect(SearchService.retrieveFilters(name)).toBe(objects[idx]);
286+ });
287 });
288 });
289 });