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