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
=== modified file 'src/maasserver/static/js/angular/controllers/nodes_list.js'
--- src/maasserver/static/js/angular/controllers/nodes_list.js 2015-05-26 13:52:53 +0000
+++ src/maasserver/static/js/angular/controllers/nodes_list.js 2015-06-03 20:11:57 +0000
@@ -45,7 +45,7 @@
45 $scope.tabs.nodes.predicate = 'fqdn';45 $scope.tabs.nodes.predicate = 'fqdn';
46 $scope.tabs.nodes.allViewableChecked = false;46 $scope.tabs.nodes.allViewableChecked = false;
47 $scope.tabs.nodes.metadata = NodesManager.getMetadata();47 $scope.tabs.nodes.metadata = NodesManager.getMetadata();
48 $scope.tabs.nodes.filters = SearchService.emptyFilter;48 $scope.tabs.nodes.filters = SearchService.getEmptyFilter();
49 $scope.tabs.nodes.column = 'fqdn';49 $scope.tabs.nodes.column = 'fqdn';
50 $scope.tabs.nodes.actionOption = null;50 $scope.tabs.nodes.actionOption = null;
51 $scope.tabs.nodes.takeActionOptions = GeneralManager.getData(51 $scope.tabs.nodes.takeActionOptions = GeneralManager.getData(
@@ -75,7 +75,7 @@
75 $scope.tabs.devices.predicate = 'fqdn';75 $scope.tabs.devices.predicate = 'fqdn';
76 $scope.tabs.devices.allViewableChecked = false;76 $scope.tabs.devices.allViewableChecked = false;
77 $scope.tabs.devices.metadata = DevicesManager.getMetadata();77 $scope.tabs.devices.metadata = DevicesManager.getMetadata();
78 $scope.tabs.devices.filters = SearchService.emptyFilter;78 $scope.tabs.devices.filters = SearchService.getEmptyFilter();
79 $scope.tabs.devices.column = 'fqdn';79 $scope.tabs.devices.column = 'fqdn';
80 $scope.tabs.devices.actionOption = null;80 $scope.tabs.devices.actionOption = null;
81 $scope.tabs.devices.takeActionOptions = GeneralManager.getData(81 $scope.tabs.devices.takeActionOptions = GeneralManager.getData(
@@ -289,7 +289,7 @@
289 var filters = SearchService.getCurrentFilters(289 var filters = SearchService.getCurrentFilters(
290 $scope.tabs[tab].search);290 $scope.tabs[tab].search);
291 if(filters === null) {291 if(filters === null) {
292 $scope.tabs[tab].filters = SearchService.emptyFilter;292 $scope.tabs[tab].filters = SearchService.getEmptyFilter();
293 $scope.tabs[tab].searchValid = false;293 $scope.tabs[tab].searchValid = false;
294 } else {294 } else {
295 $scope.tabs[tab].filters = filters;295 $scope.tabs[tab].filters = filters;
@@ -477,11 +477,27 @@
477 $scope.loading = false;477 $scope.loading = false;
478 });478 });
479479
480 // Stop polling when the scope is destroyed.480 // Stop polling and save the current filter when the scope is destroyed.
481 $scope.$on("$destroy", function() {481 $scope.$on("$destroy", function() {
482 GeneralManager.stopPolling("osinfo");482 GeneralManager.stopPolling("osinfo");
483 SearchService.storeFilters("nodes", $scope.tabs.nodes.filters);
484 SearchService.storeFilters("devices", $scope.tabs.devices.filters);
483 });485 });
484486
487 // Restore the filters if any saved.
488 var nodesFilter = SearchService.retrieveFilters("nodes");
489 if(angular.isObject(nodesFilter)) {
490 $scope.tabs.nodes.search = SearchService.filtersToString(
491 nodesFilter);
492 $scope.updateFilters("nodes");
493 }
494 var devicesFilter = SearchService.retrieveFilters("devices");
495 if(angular.isObject(devicesFilter)) {
496 $scope.tabs.devices.search = SearchService.filtersToString(
497 devicesFilter);
498 $scope.updateFilters("devices");
499 }
500
485 // Switch to the specified tab, if specified.501 // Switch to the specified tab, if specified.
486 if($routeParams.tab === "nodes" || $routeParams.tab === "devices") {502 if($routeParams.tab === "nodes" || $routeParams.tab === "devices") {
487 $scope.toggleTab($routeParams.tab);503 $scope.toggleTab($routeParams.tab);
488504
=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js'
--- src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js 2015-05-26 13:52:53 +0000
+++ src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js 2015-06-03 20:11:57 +0000
@@ -149,6 +149,18 @@
149 "osinfo");149 "osinfo");
150 });150 });
151151
152 it("saves current filters for nodes and devices when scope destroyed",
153 function() {
154 var controller = makeController();
155 var nodesFilters = {}, devicesFilters = {};
156 $scope.tabs.nodes.filters = nodesFilters;
157 $scope.tabs.devices.filters = devicesFilters;
158 $scope.$destroy();
159 expect(SearchService.retrieveFilters("nodes")).toBe(nodesFilters);
160 expect(SearchService.retrieveFilters("devices")).toBe(
161 devicesFilters);
162 });
163
152 it("calls loadManagers with NodesManager, DevicesManager," +164 it("calls loadManagers with NodesManager, DevicesManager," +
153 "GeneralManager, UsersManager",165 "GeneralManager, UsersManager",
154 function() {166 function() {
@@ -166,6 +178,24 @@
166 expect($scope.loading).toBe(false);178 expect($scope.loading).toBe(false);
167 });179 });
168180
181 it("sets nodes search from SearchService",
182 function() {
183 var query = makeName("query");
184 SearchService.storeFilters(
185 "nodes", SearchService.getCurrentFilters(query));
186 var controller = makeController();
187 expect($scope.tabs.nodes.search).toBe(query);
188 });
189
190 it("sets devices search from SearchService",
191 function() {
192 var query = makeName("query");
193 SearchService.storeFilters(
194 "devices", SearchService.getCurrentFilters(query));
195 var controller = makeController();
196 expect($scope.tabs.devices.search).toBe(query);
197 });
198
169 it("sets nodes search from $routeParams.query",199 it("sets nodes search from $routeParams.query",
170 function() {200 function() {
171 var query = makeName("query");201 var query = makeName("query");
@@ -228,7 +258,8 @@
228 expect(tabScope.selectedItems).toBe(258 expect(tabScope.selectedItems).toBe(
229 tabScope.manager.getSelectedItems());259 tabScope.manager.getSelectedItems());
230 expect(tabScope.metadata).toBe(tabScope.manager.getMetadata());260 expect(tabScope.metadata).toBe(tabScope.manager.getMetadata());
231 expect(tabScope.filters).toBe(SearchService.emptyFilter);261 expect(tabScope.filters).toEqual(
262 SearchService.getEmptyFilter());
232 expect(tabScope.column).toBe("fqdn");263 expect(tabScope.column).toBe("fqdn");
233 expect(tabScope.actionOption).toBeNull();264 expect(tabScope.actionOption).toBeNull();
234 expect(tabScope.takeActionOptions).toEqual([]);265 expect(tabScope.takeActionOptions).toEqual([]);
@@ -440,7 +471,7 @@
440 it("calls SearchService.toggleFilter", function() {471 it("calls SearchService.toggleFilter", function() {
441 var controller = makeController();472 var controller = makeController();
442 spyOn(SearchService, "toggleFilter").and.returnValue(473 spyOn(SearchService, "toggleFilter").and.returnValue(
443 SearchService.emptyFilter);474 SearchService.getEmptyFilter());
444 $scope.toggleFilter("hostname", "test", tab);475 $scope.toggleFilter("hostname", "test", tab);
445 expect(SearchService.toggleFilter).toHaveBeenCalled();476 expect(SearchService.toggleFilter).toHaveBeenCalled();
446 });477 });
@@ -507,8 +538,8 @@
507 $scope.tabs[tab].search = "test hostname:(name";538 $scope.tabs[tab].search = "test hostname:(name";
508 $scope.updateFilters(tab);539 $scope.updateFilters(tab);
509 expect(540 expect(
510 $scope.tabs[tab].filters).toBe(541 $scope.tabs[tab].filters).toEqual(
511 SearchService.emptyFilter);542 SearchService.getEmptyFilter());
512 expect($scope.tabs[tab].searchValid).toBe(false);543 expect($scope.tabs[tab].searchValid).toBe(false);
513 });544 });
514 });545 });
515546
=== modified file 'src/maasserver/static/js/angular/services/search.js'
--- src/maasserver/static/js/angular/services/search.js 2015-01-29 21:35:52 +0000
+++ src/maasserver/static/js/angular/services/search.js 2015-06-03 20:11:57 +0000
@@ -6,13 +6,21 @@
66
7angular.module('MAAS').service('SearchService', function() {7angular.module('MAAS').service('SearchService', function() {
88
9 // Holds an empty filter object.
10 var emptyFilter = { _: [] };
11
12 // Return a new empty filter;
13 this.getEmptyFilter = function() {
14 return angular.copy(emptyFilter);
15 };
16
9 // Splits the search string into different terms based on white space.17 // Splits the search string into different terms based on white space.
10 // This handles the ability for whitespace to be inside of '(', ')'.18 // This handles the ability for whitespace to be inside of '(', ')'.
11 //19 //
12 // XXX blake_r 28-01-15: This could be improved with a regex, but was20 // XXX blake_r 28-01-15: This could be improved with a regex, but was
13 // unable to come up with one that would allow me to validate the end21 // unable to come up with one that would allow me to validate the end
14 // ')' in the string.22 // ')' in the string.
15 function getSplitSearch(search) {23 this.getSplitSearch = function(search) {
16 var terms = search.split(' ');24 var terms = search.split(' ');
17 var fixedTerms = [];25 var fixedTerms = [];
18 var spanningParentheses = false;26 var spanningParentheses = false;
@@ -46,15 +54,15 @@
46 return null;54 return null;
47 }55 }
48 return fixedTerms;56 return fixedTerms;
49 }57 };
5058
51 // Return all of the currently active filters for the given search.59 // Return all of the currently active filters for the given search.
52 function getCurrentFilters(search) {60 this.getCurrentFilters = function(search) {
53 var filters = { _: [] };61 var filters = this.getEmptyFilter();
54 if(search.length === 0) {62 if(search.length === 0) {
55 return filters;63 return filters;
56 }64 }
57 var searchTerms = getSplitSearch(search);65 var searchTerms = this.getSplitSearch(search);
58 if(!searchTerms) {66 if(!searchTerms) {
59 return null;67 return null;
60 }68 }
@@ -94,10 +102,10 @@
94 }102 }
95 });103 });
96 return filters;104 return filters;
97 }105 };
98106
99 // Convert "filters" into a search string.107 // Convert "filters" into a search string.
100 function filtersToString(filters) {108 this.filtersToString = function(filters) {
101 var search = "";109 var search = "";
102 angular.forEach(filters, function(terms, type) {110 angular.forEach(filters, function(terms, type) {
103 // Skip empty and skip "_" as it gets appended at the111 // Skip empty and skip "_" as it gets appended at the
@@ -111,19 +119,19 @@
111 search = filters._.join(" ") + " " + search;119 search = filters._.join(" ") + " " + search;
112 }120 }
113 return search.trim();121 return search.trim();
114 }122 };
115123
116 // Return true if the type and value are in the filters.124 // Return true if the type and value are in the filters.
117 function isFilterActive(filters, type, value) {125 this.isFilterActive = function(filters, type, value) {
118 var values = filters[type];126 var values = filters[type];
119 if(angular.isUndefined(values)) {127 if(angular.isUndefined(values)) {
120 return false;128 return false;
121 }129 }
122 return values.indexOf(value) !== -1;130 return values.indexOf(value) !== -1;
123 }131 };
124132
125 // Toggles a filter on or off based on type and value.133 // Toggles a filter on or off based on type and value.
126 function toggleFilter(filters, type, value) {134 this.toggleFilter = function(filters, type, value) {
127 if(angular.isUndefined(filters[type])) {135 if(angular.isUndefined(filters[type])) {
128 filters[type] = [];136 filters[type] = [];
129 }137 }
@@ -134,14 +142,18 @@
134 filters[type].splice(idx, 1);142 filters[type].splice(idx, 1);
135 }143 }
136 return filters;144 return filters;
137 }145 };
138146
139 return {147 // Holds all stored filters.
140 emptyFilter: { _: [] },148 var storedFilters = {};
141 getSplitSearch: getSplitSearch,149
142 getCurrentFilters: getCurrentFilters,150 // Store a filter for later.
143 filtersToString: filtersToString,151 this.storeFilters = function(name, filters) {
144 isFilterActive: isFilterActive,152 storedFilters[name] = filters;
145 toggleFilter: toggleFilter153 };
154
155 // Retrieve a stored fitler.
156 this.retrieveFilters = function(name) {
157 return storedFilters[name];
146 };158 };
147});159});
148160
=== modified file 'src/maasserver/static/js/angular/services/tests/test_search.js'
--- src/maasserver/static/js/angular/services/tests/test_search.js 2015-01-29 21:35:52 +0000
+++ src/maasserver/static/js/angular/services/tests/test_search.js 2015-06-03 20:11:57 +0000
@@ -146,10 +146,33 @@
146 });146 });
147 });147 });
148148
149 describe("emptyFilter", function() {149 describe("getEmptyFilter", function() {
150150
151 it("includes _ empty list", function() {151 it("includes _ empty list", function() {
152 expect(SearchService.emptyFilter).toEqual({ _: [] });152 expect(SearchService.getEmptyFilter()).toEqual({ _: [] });
153 });
154
155 it("returns different object on each call", function() {
156 var one = SearchService.getEmptyFilter();
157 var two = SearchService.getEmptyFilter();
158 expect(one).not.toBe(two);
159 });
160 });
161
162 describe("storeFilters/retrieveFilters", function() {
163
164 it("stores and retrieves the same object", function() {
165 var i, names = [], objects = [];
166 for(i = 0; i < 3; i++) {
167 names.push(makeName("name"));
168 objects.push({});
169 }
170 angular.forEach(names, function(name, idx) {
171 SearchService.storeFilters(name, objects[idx]);
172 });
173 angular.forEach(names, function(name, idx) {
174 expect(SearchService.retrieveFilters(name)).toBe(objects[idx]);
175 });
153 });176 });
154 });177 });
155});178});