Catalog alignment
[sdc.git] / catalog-ui / src / app / ng2 / pages / catalog / catalog.component.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 import * as _ from "lodash";
22 import { Component as NgComponent, Inject } from '@angular/core';
23 import { SdcUiCommon, SdcUiServices } from "onap-ui-angular";
24 import { CacheService, CatalogService } from "app/services-ng2";
25 import { SdcConfigToken, ISdcConfig } from "../../config/sdc-config.config";
26 import { SdcMenuToken, IAppMenu } from "../../config/sdc-menu.config";
27 import { Component, ICategoryBase, IMainCategory, ISubCategory, IConfigStatuses, ICatalogSelector, CatalogSelectorTypes } from "app/models";
28 import { ResourceNamePipe } from "../../pipes/resource-name.pipe";
29 import { EntityFilterPipe, IEntityFilterObject, ISearchFilter} from "../../pipes/entity-filter.pipe";
30
31 interface Gui {
32     onComponentSubTypesClick:Function;
33     onComponentTypeClick:Function;
34     onCategoryClick:Function;
35     onStatusClick:Function;
36     changeFilterTerm:Function;
37 }
38
39 interface IFilterParams {
40     components: string[];
41     categories: string[];
42     statuses: (string)[];
43     order: [string, boolean];
44     term: string;
45     active: boolean;
46 }
47
48 interface ICheckboxesFilterMap {
49     [key: string]: Array<string>;
50     _main: Array<string>;
51 }
52
53 interface ICheckboxesFilterKeys {
54     componentTypes: ICheckboxesFilterMap;
55     categories: ICheckboxesFilterMap;
56     statuses: ICheckboxesFilterMap;
57 }
58
59 interface ICategoriesMap {
60     [key: string]: {
61         category: ICategoryBase,
62         parent: ICategoryBase
63     }
64 }
65
66 @NgComponent({
67     selector: 'catalog',
68     templateUrl: './catalog.component.html',
69     styleUrls:['./catalog.component.less']
70 })
71 export class CatalogComponent {
72     public checkboxesFilter:IEntityFilterObject;
73     public checkboxesFilterKeys:ICheckboxesFilterKeys;
74     public gui:Gui;
75     public categories:Array<IMainCategory>;
76     public filteredCategories:Array<IMainCategory>;
77     public confStatus:IConfigStatuses;
78     public componentTypes:{[key:string]: Array<string>};
79     public catalogItems:Array<Component>;
80     public catalogFilteredItems:Array<Component>;
81     public catalogFilteredSlicedItems:Array<Component>;
82     public expandedSection:Array<string>;
83     public version:string;
84     public sortBy:string;
85     public reverse:boolean;
86     public filterParams:IFilterParams;
87     public search:ISearchFilter;
88
89     //this is for UI paging
90     public numberOfItemToDisplay:number;
91
92     public selectedCatalogItem: ICatalogSelector;
93     public catalogSelectorItems: Array<ICatalogSelector>;
94     public showCatalogSelector: boolean;
95
96     public typesChecklistModel: SdcUiCommon.ChecklistModel;
97     public categoriesChecklistModel: SdcUiCommon.ChecklistModel;
98     public statusChecklistModel: SdcUiCommon.ChecklistModel;
99
100     private defaultFilterParams:IFilterParams = {
101         components: [],
102         categories: [],
103         statuses: [],
104         order: ['lastUpdateDate', true],
105         term: '',
106         active: true
107     };
108     private categoriesMap:ICategoriesMap;
109
110     constructor(
111         @Inject(SdcConfigToken) private sdcConfig:ISdcConfig,
112         @Inject(SdcMenuToken) public sdcMenu:IAppMenu,
113         @Inject("$state") private $state:ng.ui.IStateService,
114         private cacheService:CacheService,
115         private catalogService:CatalogService,
116         private resourceNamePipe:ResourceNamePipe,
117         private loaderService: SdcUiServices.LoaderService
118     ) {}
119
120     ngOnInit(): void {
121         this.initGui();
122         this.initLeftSwitch();
123         this.initScopeMembers();
124         this.loadFilterParams();
125         this.initCatalogData(); // Async task to get catalog from server.
126     }
127
128     private initLeftSwitch = ():void => {
129         this.showCatalogSelector = false;
130
131         this.catalogSelectorItems = [
132             {value: CatalogSelectorTypes.Active, title: "Active Items", header: "Active"},
133             {value: CatalogSelectorTypes.Archive, title: "Archive", header: "Archived"}
134         ];
135         // set active items is default
136         this.selectedCatalogItem = this.catalogSelectorItems[0];
137     };
138
139     private initCatalogData = ():void => {
140         if(this.selectedCatalogItem.value === CatalogSelectorTypes.Archive){
141             this.getArchiveCatalogItems();
142         } else {
143             this.getActiveCatalogItems();
144         }
145     };
146
147
148     private initScopeMembers = ():void => {
149         this.numberOfItemToDisplay = 0;
150         this.categories = this.makeSortedCategories(this.cacheService.get('serviceCategories').concat(this.cacheService.get('resourceCategories')))
151             .map((cat) => <IMainCategory>cat);
152         this.confStatus = this.sdcMenu.statuses;
153         this.expandedSection = ["type", "category", "status"];
154         this.catalogItems = [];
155         this.search = {FilterTerm: ""};
156         this.categoriesMap = this.initCategoriesMap();
157         this.initCheckboxesFilter();
158         this.initCheckboxesFilterKeys();
159         this.buildCheckboxLists();
160
161         this.version = this.cacheService.get('version');
162         this.sortBy = 'lastUpdateDate';
163         this.reverse = true;
164     };
165
166     private buildCheckboxLists() {
167         this.buildChecklistModelForTypes();
168         this.buildChecklistModelForCategories();
169         this.buildChecklistModelForStatuses();
170     }
171
172     private getTestIdForCheckboxByText = ( text: string ):string => {
173         return 'checkbox-' + text.toLowerCase().replace(/ /g, '');
174     }
175
176     private buildChecklistModelForTypes() {
177         this.componentTypes = {
178             Resource: ['VF', 'VFC', 'CR', 'PNF', 'CP', 'VL'],
179             Service: null
180         };
181         this.typesChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.componentTypes._main,
182             Object.keys(this.componentTypes).map((ct) => {
183                 let subChecklist = null;
184                 if (this.componentTypes[ct]) {
185                     this.checkboxesFilterKeys.componentTypes[ct] = this.checkboxesFilterKeys.componentTypes[ct] || [];
186                     subChecklist = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.componentTypes[ct],
187                         this.componentTypes[ct].map((st) => {
188                             const stKey = [ct, st].join('.');
189                             const testId = this.getTestIdForCheckboxByText(st);
190                             return new SdcUiCommon.ChecklistItemModel(st, false, this.checkboxesFilterKeys.componentTypes[ct].indexOf(stKey) !== -1, null, testId, stKey);
191                         })
192                     );
193                 }
194                 const testId = this.getTestIdForCheckboxByText(ct);
195                 return new SdcUiCommon.ChecklistItemModel(ct, false, this.checkboxesFilterKeys.componentTypes._main.indexOf(ct) !== -1, subChecklist, testId, ct);
196             })
197         );
198     }
199
200     private buildChecklistModelForCategories() {
201         this.categoriesChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.categories._main,
202             (this.filteredCategories || this.categories).map((cat) => {
203                 this.checkboxesFilterKeys.categories[cat.uniqueId] = this.checkboxesFilterKeys.categories[cat.uniqueId] || [];
204                 const subCategoriesChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.categories[cat.uniqueId],
205                     (cat.subcategories || []).map((scat) => {
206                         this.checkboxesFilterKeys.categories[scat.uniqueId] = this.checkboxesFilterKeys.categories[scat.uniqueId] || [];
207                         const groupingsChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.categories[scat.uniqueId],
208                             (scat.groupings || []).map(gcat =>
209                                 new SdcUiCommon.ChecklistItemModel(gcat.name, false, this.checkboxesFilterKeys.categories[scat.uniqueId].indexOf(gcat.uniqueId) !== -1, null, this.getTestIdForCheckboxByText(gcat.uniqueId), gcat.uniqueId))
210                         );
211                         return new SdcUiCommon.ChecklistItemModel(scat.name, false, this.checkboxesFilterKeys.categories[cat.uniqueId].indexOf(scat.uniqueId) !== -1, groupingsChecklistModel, this.getTestIdForCheckboxByText(scat.uniqueId), scat.uniqueId);
212                     })
213                 );
214                 return new SdcUiCommon.ChecklistItemModel(cat.name, false, this.checkboxesFilterKeys.categories._main.indexOf(cat.uniqueId) !== -1, subCategoriesChecklistModel, this.getTestIdForCheckboxByText(cat.uniqueId), cat.uniqueId);
215             })
216         );
217     }
218
219     private buildChecklistModelForStatuses() {
220         // For statuses checklist model, use the statuses keys as values. On applying filtering map the statuses keys to statuses values.
221         this.statusChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.statuses._main,
222             Object.keys(this.confStatus).map((sKey) => new SdcUiCommon.ChecklistItemModel(
223                 this.confStatus[sKey].name, 
224                 false, 
225                 this.checkboxesFilterKeys.statuses._main.indexOf(sKey) !== -1, 
226                 null, 
227                 this.getTestIdForCheckboxByText(sKey), 
228                 sKey))
229         );
230     }
231
232     private initCheckboxesFilter() {
233         // Checkboxes filter init
234         this.checkboxesFilter = <IEntityFilterObject>{};
235         this.checkboxesFilter.selectedComponentTypes = [];
236         this.checkboxesFilter.selectedResourceSubTypes = [];
237         this.checkboxesFilter.selectedCategoriesModel = [];
238         this.checkboxesFilter.selectedStatuses = [];
239     }
240
241     private initCheckboxesFilterKeys() {
242         // init checkboxes filter keys (for checklists values):
243         this.checkboxesFilterKeys = <ICheckboxesFilterKeys>{};
244         this.checkboxesFilterKeys.componentTypes = { _main: [] };
245         this.checkboxesFilterKeys.categories = { _main: [] };
246         this.checkboxesFilterKeys.statuses = { _main: [] };
247     }
248
249     private initCategoriesMap(categoriesList?:(ICategoryBase)[], parentCategory:ICategoryBase=null): ICategoriesMap {
250         categoriesList = (categoriesList) ? categoriesList : this.categories;
251
252         // Init categories map
253         return categoriesList.reduce((acc, cat) => {
254             acc[cat.uniqueId] = {
255                 category: cat,
256                 parent: parentCategory
257             };
258             const catChildren = ((<IMainCategory>cat).subcategories)
259                 ? (<IMainCategory>cat).subcategories
260                 : (((<ISubCategory>cat).groupings)
261                     ? (<ISubCategory>cat).groupings
262                     : null);
263             if (catChildren) {
264                 Object.assign(acc, this.initCategoriesMap(catChildren, cat));
265             }
266             return acc;
267         }, <ICategoriesMap>{});
268     }
269
270     public selectLeftSwitchItem(item: ICatalogSelector): void {
271         if (this.selectedCatalogItem.value !== item.value) {
272             this.selectedCatalogItem = item;
273             switch (item.value) {
274                 case CatalogSelectorTypes.Active:
275                     this.getActiveCatalogItems(true);
276                     break;
277
278                 case CatalogSelectorTypes.Archive:
279                     this.getArchiveCatalogItems(true);
280                     break;
281             }
282             this.changeFilterParams({active: (item.value === CatalogSelectorTypes.Active)});
283         }
284     }
285
286     public sectionClick(section: string): void {
287         let index: number = this.expandedSection.indexOf(section);
288         if (index !== -1) {
289             this.expandedSection.splice(index, 1);
290         } else {
291             this.expandedSection.push(section);
292         }
293     }
294
295
296     private makeFilterParamsFromCheckboxes(checklistModel:SdcUiCommon.ChecklistModel): Array<string> {
297         return checklistModel.checkboxes.reduce((acc, chbox) => {
298             if (checklistModel.selectedValues.indexOf(chbox.value) !== -1) {
299                 acc.push(chbox.value);
300             } else if (chbox.subLevelChecklist) {  // else, if checkbox is not checked, then try to get values from sub checklists
301                 acc.push(...this.makeFilterParamsFromCheckboxes(chbox.subLevelChecklist));
302             }
303             return acc;
304         }, []);
305     }
306
307     //default sort by descending last update. default for alphabetical = ascending
308     public order(sortBy: string): void {
309         this.changeFilterParams({
310             order: (this.filterParams.order[0] === sortBy)
311                 ? [sortBy, !this.filterParams.order[1]]
312                 : [sortBy, sortBy === 'lastUpdateDate']
313         });
314     }
315
316
317     public goToComponent(component: Component): void {
318         this.$state.go('workspace.general', {id: component.uniqueId, type: component.componentType.toLowerCase()});
319     }
320
321
322     // Will print the number of elements found in catalog
323     public getNumOfElements(num:number):string {
324         if (!num || num === 0) {
325             return `No <b>${this.selectedCatalogItem.header}</b> Elements found`;
326         } else if (num === 1) {
327             return `1 <b>${this.selectedCatalogItem.header}</b> Element found`;
328         } else {
329             return num + ` <b>${this.selectedCatalogItem.header}</b> Elements found`;
330         }
331     }
332
333     public initGui(): void {
334         this.gui = <Gui>{};
335
336         /**
337          * Select | unselect sub resource when resource is clicked | unclicked.
338          * @param type
339          */
340         this.gui.onComponentTypeClick = (): void => {
341             this.changeFilterParams({
342                 components: this.makeFilterParamsFromCheckboxes(this.typesChecklistModel)
343             });
344         };
345
346         this.gui.onCategoryClick = (): void => {
347             this.changeFilterParams({
348                 categories: this.makeFilterParamsFromCheckboxes(this.categoriesChecklistModel)
349             });
350         };
351
352         this.gui.onStatusClick = (statusChecklistItem: SdcUiCommon.ChecklistItemModel) => {
353             this.changeFilterParams({
354                 statuses: this.makeFilterParamsFromCheckboxes(this.statusChecklistModel)
355             });
356         };
357
358         this.gui.changeFilterTerm = (filterTerm: string) => {
359             this.changeFilterParams({
360                 term: filterTerm
361             });
362         };
363     }
364
365     public raiseNumberOfElementToDisplay(recalculate:boolean = false): void {
366         const scrollPageAmount = 35;
367         if (!this.catalogFilteredItems) {
368             this.numberOfItemToDisplay = 0;
369         } else if (this.catalogFilteredItems.length > this.numberOfItemToDisplay || recalculate) {
370             let fullPagesAmount = Math.ceil(this.numberOfItemToDisplay / scrollPageAmount) * scrollPageAmount;
371             if (!recalculate || fullPagesAmount === 0) {  //TODO trigger infiniteScroll to check bottom and fire onBottomHit by itself (sdc-ui)
372                 fullPagesAmount += scrollPageAmount;
373             }
374             this.numberOfItemToDisplay = Math.min(this.catalogFilteredItems.length, fullPagesAmount);
375             this.catalogFilteredSlicedItems = this.catalogFilteredItems.slice(0, this.numberOfItemToDisplay);
376         }
377     }
378
379     private isDefaultFilter = (): boolean => {
380         return angular.equals(this.defaultFilterParams, this.filterParams);
381     }
382
383     private componentShouldReload = ():boolean => {
384         let breadcrumbsValid: boolean = (this.$state.current.name === this.cacheService.get('breadcrumbsComponentsState') && this.cacheService.contains('breadcrumbsComponents'));
385         return !breadcrumbsValid || this.isDefaultFilter();
386     }
387
388     private getActiveCatalogItems(forceReload?: boolean): void {
389         if (forceReload || this.componentShouldReload()) {
390             this.loaderService.activate();
391
392             let onSuccess = (followedResponse:Array<Component>):void => {
393                 this.updateCatalogItems(followedResponse);
394                 this.loaderService.deactivate();
395                 this.cacheService.set('breadcrumbsComponentsState', this.$state.current.name);  //catalog
396                 this.cacheService.set('breadcrumbsComponents', followedResponse);
397                 
398             };
399
400             let onError = ():void => {
401                 console.info('Failed to load catalog CatalogViewModel::getActiveCatalogItems');
402                 this.loaderService.deactivate();
403             };
404             this.catalogService.getCatalog().subscribe(onSuccess, onError);
405         } else {
406             let cachedComponents = this.cacheService.get('breadcrumbsComponents');
407             this.updateCatalogItems(cachedComponents);
408         }
409     }
410
411     private getArchiveCatalogItems(forceReload?: boolean): void {
412         if(forceReload || !this.cacheService.contains("archiveComponents")) {
413             this.loaderService.activate();
414             let onSuccess = (followedResponse:Array<Component>):void => {
415                 this.cacheService.set("archiveComponents", followedResponse);
416                 this.loaderService.deactivate();
417                 this.updateCatalogItems(followedResponse);
418             };
419
420             let onError = ():void => {
421                 console.info('Failed to load catalog CatalogViewModel::getArchiveCatalogItems');
422                 this.loaderService.deactivate();
423             };
424
425             this.catalogService.getArchiveCatalog().subscribe(onSuccess, onError);
426         } else {
427             let archiveCache = this.cacheService.get("archiveComponents");
428             this.updateCatalogItems(archiveCache);
429         }
430     }
431
432     private updateCatalogItems = (items:Array<Component>):void => {
433         this.catalogItems = items;
434         this.catalogItems.forEach(this.addFilterTermToComponent);
435         this.filterCatalogItems();
436     }
437
438     private applyFilterParamsToView(filterParams:IFilterParams) {
439         // reset checkboxes filter
440         this.initCheckboxesFilter();
441
442         this.filterCatalogCategories();
443
444         this.applyFilterParamsComponents(filterParams);
445         this.applyFilterParamsCategories(filterParams);
446         this.applyFilterParamsStatuses(filterParams);
447         this.applyFilterParamsOrder(filterParams);
448         this.applyFilterParamsTerm(filterParams);
449
450         // do filters when filter params are changed:
451         this.filterCatalogItems();
452     }
453
454     private filterCatalogCategories() {
455         this.filteredCategories = this.makeFilteredCategories(this.categories, this.checkboxesFilter.selectedComponentTypes);
456         this.buildChecklistModelForCategories();
457     }
458
459     private filterCatalogItems() {
460         this.catalogFilteredItems = this.makeFilteredItems(this.catalogItems, this.checkboxesFilter, this.search, this.sortBy, this.reverse);
461         this.raiseNumberOfElementToDisplay(true);
462         this.catalogFilteredSlicedItems = this.catalogFilteredItems.slice(0, this.numberOfItemToDisplay);
463     }
464
465     private applyFilterParamsToCheckboxes(checklistModel:SdcUiCommon.ChecklistModel, filterParamsList:Array<string>) {
466         checklistModel.checkboxes.forEach((chbox) => {
467             // if checkbox is checked, then add it to selected values if not there, and select all sub checkboxes
468             if (filterParamsList.indexOf(chbox.value) !== -1 && checklistModel.selectedValues.indexOf(chbox.value) === -1) {
469                 checklistModel.selectedValues.push(chbox.value);
470                 if (chbox.subLevelChecklist) {
471                     this.applyFilterParamsToCheckboxes(chbox.subLevelChecklist, chbox.subLevelChecklist.checkboxes.map((subchbox) => subchbox.value));
472                 }
473             } else if ( chbox.subLevelChecklist ) {
474                 this.applyFilterParamsToCheckboxes(chbox.subLevelChecklist, filterParamsList);
475             }
476         });
477     }
478
479     private applyFilterParamsComponents(filterParams:IFilterParams) {
480         this.applyFilterParamsToCheckboxes(this.typesChecklistModel, filterParams.components);
481         this.checkboxesFilter.selectedComponentTypes = this.checkboxesFilterKeys.componentTypes._main;
482         Object.keys(this.checkboxesFilterKeys.componentTypes).forEach((chKey) => {
483             if (chKey !== '_main') {
484                 this.checkboxesFilter['selected' + chKey + 'SubTypes'] = this.checkboxesFilterKeys.componentTypes[chKey].map((st) => st.substr(chKey.length + 1));
485             }
486         });
487
488         let selectedCatalogIndex = filterParams.active ? CatalogSelectorTypes.Active : CatalogSelectorTypes.Archive;
489         this.selectedCatalogItem = this.catalogSelectorItems[selectedCatalogIndex];
490     }
491
492     private applyFilterParamsCategories(filterParams:IFilterParams) {
493         this.applyFilterParamsToCheckboxes(this.categoriesChecklistModel, filterParams.categories);
494         this.checkboxesFilter.selectedCategoriesModel = <Array<string>>_.flatMap(this.checkboxesFilterKeys.categories);
495     }
496
497     private applyFilterParamsStatuses(filterParams: IFilterParams) {
498         this.applyFilterParamsToCheckboxes(this.statusChecklistModel, filterParams.statuses);
499         this.checkboxesFilter.selectedStatuses = _.reduce(_.flatMap(this.checkboxesFilterKeys.statuses), (stats, st:string) => [...stats, ...this.confStatus[st].values], []);
500     }
501
502     private applyFilterParamsOrder(filterParams: IFilterParams) {
503         this.sortBy = filterParams.order[0];
504         this.reverse = filterParams.order[1];
505     }
506
507     private applyFilterParamsTerm(filterParams: IFilterParams) {
508         this.search = {
509             filterTerm: filterParams.term
510         };
511     }
512
513     private loadFilterParams() {
514         const params = this.$state.params;
515         this.filterParams = angular.copy(this.defaultFilterParams);
516         Object.keys(params).forEach((k) => {
517             if (!angular.isUndefined(params[k])) {
518                 let newVal;
519                 let paramsChecklist: SdcUiCommon.ChecklistModel = null;
520                 let filterKey = k.substr('filter.'.length);
521                 switch (k) {
522                     case 'filter.components':
523                         paramsChecklist = paramsChecklist || this.typesChecklistModel;
524                     case 'filter.categories':
525                         paramsChecklist = paramsChecklist || this.categoriesChecklistModel;
526                     case 'filter.statuses':
527                         paramsChecklist = paramsChecklist || this.statusChecklistModel;
528
529                         // for those cases above - split param by comma and make reduced checklist values for filter params (url)
530                         newVal = _.uniq(params[k].split(','));
531                         break;
532                     case 'filter.order':
533                         newVal = params[k].startsWith('-') ? [params[k].substr(1), true] : [params[k], false];
534                         break;
535                     case 'filter.term':
536                         newVal = params[k];
537                         break;
538                     case 'filter.active':
539                         newVal = (params[k] === "true" || params[k] === true)? true : false;
540                         break;
541                     default:
542                         // unknown filter key
543                         filterKey = null;
544                 }
545                 if (filterKey) {
546                     this.filterParams[filterKey] = newVal;
547                 }
548             }
549         });
550         // re-set filter params with valid values, and then re-build checklists
551         this.changeFilterParams(this.filterParams, true);
552     }
553
554     private changeFilterParams(changedFilterParams, rebuild:boolean = false) {
555         const newParams = {};
556         Object.keys(changedFilterParams).forEach((k) => {
557             let newVal;
558             switch (k) {
559                 case 'components':
560                 case 'categories':
561                 case 'statuses':
562                     newVal = changedFilterParams[k] && changedFilterParams[k].length ? changedFilterParams[k].join(',') : null;
563                     break;
564                 case 'order':
565                     newVal = (changedFilterParams[k][1] ? '-' : '') + changedFilterParams[k][0];
566                     break;
567                 case 'term':
568                     newVal = changedFilterParams[k] ? changedFilterParams[k] : null;
569                     break;
570                 case 'active':
571                     newVal = (changedFilterParams[k] === "true" || changedFilterParams[k] === true);
572                     break;
573                 default:
574                     return;
575             }
576             this.filterParams[k] = changedFilterParams[k];
577             newParams['filter.' + k] = newVal;
578         });
579         this.$state.go('.', newParams, {location: 'replace', notify: false}).then(() => {
580             if (rebuild) {
581                 // fix the filter params to only valid values for checkboxes
582                 this.changeFilterParams({
583                     components: this.makeFilterParamsFromCheckboxes(this.typesChecklistModel),
584                     categories: this.makeFilterParamsFromCheckboxes(this.categoriesChecklistModel),
585                     statuses: this.makeFilterParamsFromCheckboxes(this.statusChecklistModel)
586                 });
587                 // rebuild the checkboxes to show selected
588                 this.buildCheckboxLists();
589             }
590         });
591         this.applyFilterParamsToView(this.filterParams);
592     }
593
594     private makeFilteredCategories(categories:Array<IMainCategory>, selectedTypes:Array<string>=[]): Array<IMainCategory> {
595         let filteredCategories = categories.slice();
596
597         const filteredMainTypes = selectedTypes.reduce((acc, st) => {
598             const mainType = st.split('.')[0];
599             if (acc.indexOf(mainType) === -1) {
600                 acc.push(mainType);
601             }
602             return acc;
603         }, []);
604
605         // filter by selected types
606         if (filteredMainTypes.length) {
607             const filteredTypesCategories = filteredMainTypes.reduce((acc, mainType: string) => {
608                 acc.push(...this.cacheService.get(mainType.toLowerCase() + 'Categories'));
609                 return acc;
610             }, []);
611
612             filteredCategories = _.intersectionBy(filteredCategories, filteredTypesCategories, c => c.uniqueId);
613         }
614
615         return filteredCategories;
616     }
617
618     private makeSortedCategories(categories:Array<IMainCategory|ISubCategory|ICategoryBase>, sortBy?:any): Array<IMainCategory|ISubCategory|ICategoryBase> {
619         sortBy = (sortBy !== undefined) ? sortBy : ['name'];
620         let sortedCategories = categories.map(cat => Object.assign({}, cat));  // copy each object in the array
621         sortedCategories = _.sortBy(sortedCategories, sortBy);
622
623         // inner sort of subcategories and groupings
624         sortedCategories.forEach((cat) => {
625             if ('subcategories' in cat && cat['subcategories'] && cat['subcategories'].length > 0) {
626                 cat['subcategories'] = this.makeSortedCategories(cat['subcategories'], sortBy);
627             }
628             if ('groupings' in cat && cat['groupings'] && cat['groupings'].length > 0) {
629                 cat['groupings'] = this.makeSortedCategories(cat['groupings'], sortBy);
630             }
631         });
632
633         return sortedCategories;
634     }
635
636     private addFilterTermToComponent(component:Component) {
637         component.filterTerm = component.name +  ' '  + component.description + ' ' + component.tags.toString() + ' ' + component.version;
638         component.filterTerm = component.filterTerm.toLowerCase();
639     }
640
641     private makeFilteredItems(catalogItems:Array<Component>, filter:IEntityFilterObject, search:ISearchFilter, sortBy:string, reverse:boolean) {
642         let filteredComponents:Array<Component> = catalogItems;
643
644         // common entity filter
645         // --------------------------------------------------------------------------
646         filter = Object.assign({ search }, filter);  // add search to entity filter object
647         filteredComponents = EntityFilterPipe.transform(filteredComponents, filter);
648
649         // sort
650         // --------------------------------------------------------------------------
651         if (sortBy) {
652             switch (sortBy) {
653                 case 'resourceName':
654                     filteredComponents = _.sortBy(filteredComponents, cat => this.resourceNamePipe.transform(cat.name));
655                     break;
656                 default:
657                     filteredComponents = _.sortBy(filteredComponents, [sortBy]);
658             }
659             if (reverse) {
660                 _.reverse(filteredComponents);
661             }
662         }
663
664         return filteredComponents;
665     }
666 }