re base code
[sdc.git] / catalog-ui / src / app / view-models / catalog / catalog-view-model.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 'use strict';
22 import * as _ from "lodash";
23 import {Component, IMainCategory, IGroup, IConfigStatuses, IAppMenu, IAppConfigurtaion, IUserProperties, ISubCategory, ICategoryBase} from "app/models";
24 import {EntityService, CacheService} from "app/services";
25 import {ComponentFactory, ResourceType, MenuHandler, ChangeLifecycleStateHandler} from "app/utils";
26 import {UserService} from "../../ng2/services/user.service";
27 import {ArchiveService} from "../../ng2/services/archive.service";
28 import { ICatalogSelector, CatalogSelectorTypes } from "../../models/catalogSelector";
29 import {IConfigStatus} from "../../models/app-config";
30
31 interface Checkboxes {
32     componentTypes:Array<string>;
33     resourceSubTypes:Array<string>;
34 }
35
36 interface CheckboxesFilter {
37     // Types
38     selectedComponentTypes:Array<string>;
39     selectedResourceSubTypes:Array<string>;
40     // Categories
41     selectedCategoriesModel:Array<string>;
42     // Statuses
43     selectedStatuses:Array<Array<string>>;
44 }
45
46 interface Gui {
47     isLoading:boolean;
48     onComponentSubTypesClick:Function;
49     onComponentTypeClick:Function;
50     onCategoryClick:Function;
51     onStatusClick:Function;
52     changeFilterTerm:Function;
53 }
54
55 interface IFilterParams {
56     components: string[];
57     categories: string[];
58     statuses: (string)[];
59     order: [string, boolean];
60     term: string;
61     active: boolean;
62 }
63
64 interface ICategoriesMap {
65     [key: string]: {
66         category: ICategoryBase,
67         parent: ICategoryBase
68     }
69 }
70
71 export interface ICatalogViewModelScope extends ng.IScope {
72     checkboxes:Checkboxes;
73     checkboxesFilter:CheckboxesFilter;
74     gui:Gui;
75
76     categories:Array<IMainCategory>;
77     confStatus:IConfigStatuses;
78     sdcMenu:IAppMenu;
79     catalogFilterdItems:Array<Component>;
80     expandedSection:Array<string>;
81     actionStrategy:any;
82     user:IUserProperties;
83     catalogMenuItem:any;
84     version:string;
85     sortBy:string;
86     reverse:boolean;
87     vfcmtType:string;
88
89     //this is for UI paging
90     numberOfItemToDisplay:number;
91     isAllItemDisplay:boolean;
92     catalogFilteredItemsNum:number;
93     changeLifecycleState(entity:any, state:string):void;
94     sectionClick (section:string):void;
95     order(sortBy:string):void;
96     getElementFoundTitle(num:number):string;
97     goToComponent(component:Component):void;
98     raiseNumberOfElementToDisplay():void;
99
100     selectedCatalogItem: ICatalogSelector;
101     catalogSelectorItems: Array<ICatalogSelector>;
102     showCatalogSelector: boolean;
103     catalogAllItems:Array<Component>; /* fake data */
104     elementFoundTitle: string;
105     elementTypeTitle: string;
106
107     selectLeftSwitchItem (item: ICatalogSelector): void;
108 }
109
110 export class CatalogViewModel {
111     static '$inject' = [
112         '$scope',
113         '$filter',
114         'Sdc.Services.EntityService',
115         'sdcConfig',
116         'sdcMenu',
117         '$state',
118         '$q',
119         'UserServiceNg2',
120         'Sdc.Services.CacheService',
121         'ComponentFactory',
122         'ChangeLifecycleStateHandler',
123         'MenuHandler',
124         'ArchiveServiceNg2'
125     ];
126
127     private defaultFilterParams:IFilterParams = {
128         components: [],
129         categories: [],
130         statuses: [],
131         order: ['lastUpdateDate', true],
132         term: '',
133         active: true
134     };
135     private categoriesMap:ICategoriesMap;
136
137     constructor(private $scope:ICatalogViewModelScope,
138                 private $filter:ng.IFilterService,
139                 private EntityService:EntityService,
140                 private sdcConfig:IAppConfigurtaion,
141                 private sdcMenu:IAppMenu,
142                 private $state:ng.ui.IStateService,
143                 private $q:ng.IQService,
144                 private userService:UserService,
145                 private cacheService:CacheService,
146                 private ComponentFactory:ComponentFactory,
147                 private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler,
148                 private MenuHandler:MenuHandler,
149                 private ArchiveService:ArchiveService
150             ) {
151
152
153         this.initLeftSwitch();
154         this.initScopeMembers();
155         this.loadFilterParams(); 
156         this.initCatalogData(); // Async task to get catalog from server.
157         this.initScopeMethods();
158     }
159
160
161     private initLeftSwitch = ():void => {
162         this.$scope.showCatalogSelector = false;
163
164         this.$scope.catalogSelectorItems = [
165             {value: CatalogSelectorTypes.Active, title: "Active Items", header: "Active"},
166             {value: CatalogSelectorTypes.Archive, title: "Archive", header: "Archived"}
167         ];
168         // set active items is default
169         this.$scope.selectedCatalogItem = this.$scope.catalogSelectorItems[0];
170     };
171
172     private initCatalogData = ():void => {
173         if(this.$scope.selectedCatalogItem.value === CatalogSelectorTypes.Archive){
174             this.getArchiveCatalogItems();
175         } else {
176             this.getActiveCatalogItems();
177         }
178     };
179
180     private initScopeMembers = ():void => {
181         // Gui init
182         this.$scope.gui = <Gui>{};
183         this.$scope.numberOfItemToDisplay = 0;
184         this.$scope.categories = this.cacheService.get('serviceCategories').concat(this.cacheService.get('resourceCategories')).map((cat) => <IMainCategory>cat);
185         this.$scope.sdcMenu = this.sdcMenu;
186         this.$scope.confStatus = this.sdcMenu.statuses;
187         this.$scope.expandedSection = ["type", "category", "status"];
188         this.$scope.user = this.userService.getLoggedinUser();
189         this.$scope.catalogMenuItem = this.sdcMenu.catalogMenuItem;
190
191         // Checklist init
192         this.$scope.checkboxes = <Checkboxes>{};
193         this.$scope.checkboxes.componentTypes = ['Resource', 'Service'];
194         this.$scope.checkboxes.resourceSubTypes = ['VF', 'VFC', 'CR', 'PNF', 'CP', 'VL'];
195         this.categoriesMap = this.initCategoriesMap();
196
197         this.initCheckboxesFilter();
198         this.$scope.version = this.cacheService.get('version');
199         this.$scope.sortBy = 'lastUpdateDate';
200         this.$scope.reverse = true;
201
202     };
203
204     private initCheckboxesFilter() {
205         // Checkboxes filter init
206         this.$scope.checkboxesFilter = <CheckboxesFilter>{};
207         this.$scope.checkboxesFilter.selectedComponentTypes = [];
208         this.$scope.checkboxesFilter.selectedResourceSubTypes = [];
209         this.$scope.checkboxesFilter.selectedCategoriesModel = [];
210         this.$scope.checkboxesFilter.selectedStatuses = [];
211     }
212
213     private initCategoriesMap(categoriesList?:(ICategoryBase)[], parentCategory:ICategoryBase=null): ICategoriesMap {
214         categoriesList = (categoriesList) ? categoriesList : this.$scope.categories;
215
216         // Init categories map
217         return categoriesList.reduce((acc, cat) => {
218             acc[cat.uniqueId] = {
219                 category: cat,
220                 parent: parentCategory
221             };
222             const catChildren = ((<IMainCategory>cat).subcategories)
223                 ? (<IMainCategory>cat).subcategories
224                 : (((<ISubCategory>cat).groupings)
225                     ? (<ISubCategory>cat).groupings
226                     : null);
227             if (catChildren) {
228                 Object.assign(acc, this.initCategoriesMap(catChildren, cat));
229             }
230             return acc;
231         }, <ICategoriesMap>{});
232     }
233
234     private initScopeMethods = ():void => {
235         this.$scope.selectLeftSwitchItem = (item: ICatalogSelector): void => {
236
237             if (this.$scope.selectedCatalogItem.value !== item.value) {
238                 this.$scope.selectedCatalogItem = item;
239                 switch (item.value) {
240                     case CatalogSelectorTypes.Active:
241                         this.getActiveCatalogItems(true);
242                         break;
243
244                     case CatalogSelectorTypes.Archive:
245                         this.getArchiveCatalogItems(true);
246                         break;
247                 }
248                 this.changeFilterParams({active: (item.value === CatalogSelectorTypes.Active)})
249             }
250         };
251
252         this.$scope.sectionClick = (section: string): void => {
253             let index: number = this.$scope.expandedSection.indexOf(section);
254             if (index !== -1) {
255                 this.$scope.expandedSection.splice(index, 1);
256             } else {
257                 this.$scope.expandedSection.push(section);
258             }
259         };
260
261
262         this.$scope.order = (sortBy: string): void => {//default sort by descending last update. default for alphabetical = ascending
263             this.changeFilterParams({
264                 order: (this.$scope.filterParams.order[0] === sortBy)
265                     ? [sortBy, !this.$scope.filterParams.order[1]]
266                     : [sortBy, sortBy === 'lastUpdateDate']
267             });
268         };
269
270
271         this.$scope.goToComponent = (component: Component): void => {
272             this.$scope.gui.isLoading = true;
273             this.$state.go('workspace.general', {id: component.uniqueId, type: component.componentType.toLowerCase()});
274         };
275
276
277         // Will print the number of elements found in catalog
278         this.$scope.getNumOfElements = (num:number):string => {
279             if (!num || num === 0) {
280                 return `No <b>${this.$scope.selectedCatalogItem.header}</b> Elements found`;
281             } else if (num === 1) {
282                 return `1 <b>${this.$scope.selectedCatalogItem.header}</b> Element found`;
283             } else {
284                 return num + ` <b>${this.$scope.selectedCatalogItem.header}</b> Elements found`;
285             }
286         };
287
288         /**
289          * Select | unselect sub resource when resource is clicked | unclicked.
290          * @param type
291          */
292         this.$scope.gui.onComponentTypeClick = (compType: string, checked?: boolean): void => {
293             let components = angular.copy(this.$scope.filterParams.components);
294             const compIdx = components.indexOf(compType);
295             checked = (checked !== undefined) ? checked : compIdx === -1;
296             if (checked && compIdx === -1) {
297                 components.push(compType);
298                 components = this.cleanSubsFromList(components);
299             } else if (!checked && compIdx !== -1) {
300                 components.splice(compIdx, 1);
301             }
302             this.changeFilterParams({
303                 components: components
304             });
305         };
306
307         /**
308          * Selecting | unselect resources when sub resource is clicked | unclicked.
309          */
310         this.$scope.gui.onComponentSubTypesClick = (compSubType: string, compType: string, checked?: boolean): void => {
311             const componentSubTypesCheckboxes = this.$scope.checkboxes[compType.toLowerCase() + 'SubTypes'];
312             if (componentSubTypesCheckboxes) {
313                 let components = angular.copy(this.$scope.filterParams.components);
314                 let componentSubTypes = components.filter((st) => st.startsWith(compType + '.'));
315
316                 const compSubTypeValue = compType + '.' + compSubType;
317                 const compSubTypeValueIdx = components.indexOf(compSubTypeValue);
318                 checked = (checked !== undefined) ? checked : compSubTypeValueIdx === -1;
319                 if (checked && compSubTypeValueIdx === -1) {
320                     components.push(compSubTypeValue);
321                     componentSubTypes.push(compSubTypeValue);
322
323                     // if all sub types are checked, then check the main component type
324                     if (componentSubTypes.length === componentSubTypesCheckboxes.length) {
325                         this.$scope.gui.onComponentTypeClick(compType, true);
326                         return;
327                     }
328                 } else if (!checked) {
329                     const compIdx = components.indexOf(compType);
330                     // if sub type exists, then remove it
331                     if (compSubTypeValueIdx !== -1) {
332                         components.splice(compSubTypeValueIdx, 1);
333                     }
334                     // else, if sub type doesn't exists, but its parent main component type exists,
335                     // then remove the main type and push all sub types except the current
336                     else if (compIdx !== -1) {
337                         components.splice(compIdx, 1);
338                         componentSubTypesCheckboxes.forEach((st) => {
339                             if (st !== compSubType) {
340                                 components.push(compType + '.' + st);
341                             }
342                         });
343                     }
344                 }
345
346                 this.changeFilterParams({
347                     components
348                 });
349             }
350         };
351
352         this.$scope.gui.onCategoryClick = (category: ICategoryBase, checked?: boolean): void => {
353             let categories: string[] = angular.copy(this.$scope.filterParams.categories);
354             let parentCategory: ICategoryBase = this.categoriesMap[category.uniqueId].parent;
355
356             // add the category to selected categories list
357             const categoryIdx = categories.indexOf(category.uniqueId);
358             checked = (checked !== undefined) ? checked : categoryIdx === -1;
359             if (checked && categoryIdx === -1) {
360                 categories.push(category.uniqueId);
361
362                 // check if all parent category children are checked, then check the parent category
363                 if (parentCategory) {
364                     if (this.getParentCategoryChildren(parentCategory).every((ch) => categories.indexOf(ch.uniqueId) !== -1)) {
365                         this.$scope.gui.onCategoryClick(parentCategory, true);
366                         return;
367                     }
368                 }
369
370                 categories = this.cleanSubsFromList(categories);
371             } else if (!checked) {
372                 // if category exists, then remove it
373                 if (categoryIdx !== -1) {
374                     categories.splice(categoryIdx, 1);
375                 }
376                 // else, if category doesn't exists, but one of its parent categories exists,
377                 // then remove that parent category and push all its children categories except the current
378                 else {
379                     let prevParentCategory: ICategoryBase = category;
380                     let additionalCategories: string[] = [];
381                     while (parentCategory) {
382                         // add parent category children to list for replacing the parent category (if will be found later)
383                         additionalCategories = additionalCategories.concat(
384                             this.getParentCategoryChildren(parentCategory)
385                                 .filter((ch) => ch.uniqueId !== prevParentCategory.uniqueId)
386                                 .map((ch) => ch.uniqueId));
387
388                         const parentCategoryIdx = categories.indexOf(parentCategory.uniqueId);
389                         if (parentCategoryIdx !== -1) {
390                             categories.splice(parentCategoryIdx, 1);
391                             categories = categories.concat(additionalCategories);
392                             break;
393                         } else {
394                             prevParentCategory = parentCategory;
395                             parentCategory = this.categoriesMap[parentCategory.uniqueId].parent;
396                         }
397                     }
398                 }
399             }
400
401             this.changeFilterParams({
402                 categories
403             });
404         };
405
406         this.$scope.gui.onStatusClick = (statusKey: string, status: IConfigStatus, checked?: boolean) => {
407             const statuses = angular.copy(this.$scope.filterParams.statuses);
408
409             // add the status key to selected statuses list
410             const statusIdx = statuses.indexOf(statusKey);
411             checked = (checked !== undefined) ? checked : statusIdx === -1;
412             if (checked && statusIdx === -1) {
413                 statuses.push(statusKey);
414             } else if (!checked && statusIdx !== -1) {
415                 statuses.splice(statusIdx, 1);
416             }
417
418             this.changeFilterParams({
419                 statuses
420             });
421         };
422
423         this.$scope.gui.changeFilterTerm = (filterTerm: string) => {
424             this.changeFilterParams({
425                 term: filterTerm
426             });
427         };
428
429         this.$scope.raiseNumberOfElementToDisplay = (): void => {
430             this.$scope.numberOfItemToDisplay = this.$scope.numberOfItemToDisplay + 35;
431             if (this.$scope.catalogFilterdItems) {
432                 this.$scope.isAllItemDisplay = this.$scope.numberOfItemToDisplay >= this.$scope.catalogFilterdItems.length;
433             }
434         };
435
436     }
437
438     private getAllCategoryChildrenIdsFlat(category:ICategoryBase) {
439         let catChildrenIds = [];
440         if ((<IMainCategory>category).subcategories) {
441             catChildrenIds = (<IMainCategory>category).subcategories.reduce((acc, scat) => {
442                     return acc.concat(this.getAllCategoryChildrenIdsFlat(scat));
443                 }, (<IMainCategory>category).subcategories.map((scat) => scat.uniqueId));
444         }
445         else if ((<ISubCategory>category).groupings) {
446             catChildrenIds = (<ISubCategory>category).groupings.map((g) => g.uniqueId);
447         }
448         return catChildrenIds;
449     }
450
451     private getParentCategoryChildren(parentCategory:ICategoryBase): ICategoryBase[] {
452         if ((<IMainCategory>parentCategory).subcategories) {
453             return (<IMainCategory>parentCategory).subcategories;
454         } else if ((<ISubCategory>parentCategory).groupings) {
455             return (<ISubCategory>parentCategory).groupings;
456         }
457         return [];
458     }
459
460     private cleanSubsFromList(list:Array<string>, delimiter:string='.', removeSubsList?:Array<string>) {
461         let curRemoveSubsList = (removeSubsList || list).slice().sort();  // by default remove any children of any item in list
462         while (curRemoveSubsList.length) {
463             const curRemoveSubItem = curRemoveSubsList.shift();
464             const removeSubListFilter = (x) => !x.startsWith(curRemoveSubItem + delimiter);
465             list = list.filter(removeSubListFilter);
466             curRemoveSubsList = curRemoveSubsList.filter(removeSubListFilter);
467         }
468         return list;
469     }
470
471     private applyFilterParamsToView(filterParams:IFilterParams) {
472         // reset checkboxes filter
473         this.initCheckboxesFilter();
474
475         this.applyFilterParamsComponents(filterParams);
476         this.applyFilterParamsCategories(filterParams);
477         this.applyFilterParamsStatuses(filterParams);
478         this.applyFilterParamsOrder(filterParams);
479         this.applyFilterParamsTerm(filterParams);
480     }
481
482     private applyFilterParamsComponents(filterParams:IFilterParams) {
483         const componentList = [];
484         const componentSubTypesLists = {};
485         filterParams.components.forEach((compStr) => {
486             const compWithSub = compStr.split('.', 2);
487             const mainComp = compWithSub[0];
488             const subComp = compWithSub[1];
489             if (!subComp) {  // main component type
490                 componentList.push(mainComp);
491
492                 // if component type has sub types list, then add all component sub types
493                 const checkboxesSubTypeKey = mainComp.toLowerCase() + 'SubTypes';
494                 if (this.$scope.checkboxes.hasOwnProperty(checkboxesSubTypeKey)) {
495                     componentSubTypesLists[mainComp] = angular.copy(this.$scope.checkboxes[checkboxesSubTypeKey]);
496                 }
497             } else {  // sub component type
498                 // init component sub types list
499                 if (!componentSubTypesLists.hasOwnProperty(mainComp)) {
500                     componentSubTypesLists[mainComp] = [];
501                 }
502                 // add sub type to list if not exist
503                 if (componentSubTypesLists[mainComp].indexOf(subComp) === -1) {
504                     componentSubTypesLists[mainComp].push(subComp);
505                 }
506             }
507         });
508         this.$scope.checkboxesFilter.selectedComponentTypes = componentList;
509         Object.keys(componentSubTypesLists).forEach((tKey) => {
510             const compSelectedSubTypeKey = 'selected' + tKey + 'SubTypes';
511             if (this.$scope.checkboxesFilter.hasOwnProperty(compSelectedSubTypeKey)) {
512                 this.$scope.checkboxesFilter[compSelectedSubTypeKey] = componentSubTypesLists[tKey];
513             }
514         });
515
516         let selectedCatalogIndex = filterParams.active ? CatalogSelectorTypes.Active : CatalogSelectorTypes.Archive;
517         this.$scope.selectedCatalogItem = this.$scope.catalogSelectorItems[selectedCatalogIndex];
518         
519     }
520
521     private applyFilterParamsCategories(filterParams:IFilterParams) {
522         this.$scope.checkboxesFilter.selectedCategoriesModel = filterParams.categories.reduce((acc, c) => {
523             acc.push(c);
524             const cat = this.categoriesMap[c].category;
525             if (cat) {
526                 acc = acc.concat(this.getAllCategoryChildrenIdsFlat(cat));
527             }
528             return acc;
529         }, []);
530     }
531
532     private getActiveCatalogItems(forceReload?: boolean): void {
533
534         if (forceReload || this.componentShouldReload()) {
535             this.$scope.gui.isLoading = true;
536             let onSuccess = (followedResponse:Array<Component>):void => {
537                 this.updateCatalogItems(followedResponse);
538                 this.$scope.gui.isLoading = false;
539                 this.cacheService.set('breadcrumbsComponentsState', this.$state.current.name);  //catalog
540                 this.cacheService.set('breadcrumbsComponents', followedResponse);
541             };
542
543             let onError = ():void => {
544                 console.info('Failed to load catalog CatalogViewModel::getActiveCatalogItems');
545                 this.$scope.gui.isLoading = false;
546             };
547             this.EntityService.getCatalog().then(onSuccess, onError);
548         } else {
549             let cachedComponents = this.cacheService.get('breadcrumbsComponents');
550             this.updateCatalogItems(cachedComponents);
551         }
552     }
553
554     private getArchiveCatalogItems(forceReload?: boolean): void {
555         if(forceReload || !this.cacheService.contains("archiveComponents")) {
556             this.$scope.gui.isLoading = true;
557             let onSuccess = (followedResponse:Array<Component>):void => {
558                 this.cacheService.set("archiveComponents", followedResponse);
559                 this.updateCatalogItems(followedResponse);
560                 this.$scope.gui.isLoading = false;
561             };
562     
563             let onError = ():void => {
564                 console.info('Failed to load catalog CatalogViewModel::getArchiveCatalogItems');
565                 this.$scope.gui.isLoading = false;
566             };
567     
568             this.ArchiveService.getArchiveCatalog().subscribe(onSuccess, onError);
569         } else {
570             let archiveCache = this.cacheService.get("archiveComponents");
571             this.updateCatalogItems(archiveCache);
572         }
573
574     }
575
576     private updateCatalogItems = (items:Array<Component>):void => {
577         this.$scope.catalogFilterdItems = items;
578         this.$scope.isAllItemDisplay = this.$scope.numberOfItemToDisplay >= this.$scope.catalogFilterdItems.length;
579         this.$scope.categories = this.cacheService.get('serviceCategories').concat(this.cacheService.get('resourceCategories'));
580     }
581
582     private componentShouldReload = ():boolean => {
583         let breadcrumbsValid: boolean = (this.$state.current.name === this.cacheService.get('breadcrumbsComponentsState') && this.cacheService.contains('breadcrumbsComponents'));
584         return !breadcrumbsValid || this.isDefaultFilter();
585     }
586
587     private isDefaultFilter = (): boolean => {
588         return angular.equals(this.defaultFilterParams, this.$scope.filterParams);
589     }
590
591     private applyFilterParamsStatuses(filterParams: IFilterParams) {
592         this.$scope.checkboxesFilter.selectedStatuses = filterParams.statuses.reduce((acc, stKey:string) => {
593             const status = this.$scope.confStatus[stKey];
594             if (status) {
595                 acc.push(status.values);
596             }
597             return acc;
598         }, []);
599     }
600
601     private applyFilterParamsOrder(filterParams: IFilterParams) {
602         this.$scope.sortBy = filterParams.order[0];
603         this.$scope.reverse = filterParams.order[1];
604     }
605
606     private applyFilterParamsTerm(filterParams: IFilterParams) {
607         this.$scope.search = {
608             filterTerm: filterParams.term
609         };
610     }
611
612     private loadFilterParams() {
613         const params = this.$state.params;
614         this.$scope.filterParams = angular.copy(this.defaultFilterParams);
615         Object.keys(params).forEach((k) => {
616             if (!angular.isUndefined(params[k])) {
617                 let newVal;
618                 let filterKey = k.substr('filter.'.length);
619                 switch (k) {
620                     case 'filter.components':
621                     case 'filter.categories':
622                         newVal = _.uniq(params[k].split(','));
623                         newVal = this.cleanSubsFromList(newVal);
624                         break;
625                     case 'filter.statuses':
626                         newVal = _.uniq(params[k].split(','));
627                         break;
628                     case 'filter.order':
629                         newVal = params[k].startsWith('-') ? [params[k].substr(1), true] : [params[k], false];
630                         break;
631                     case 'filter.term':
632                         newVal = params[k];
633                         break;
634                     case 'filter.active':
635                         newVal = (params[k] === "true" || params[k] === true);
636                         break;
637                     default:
638                         // unknown filter key
639                         filterKey = null;
640                 }
641                 if (filterKey) {
642                     this.$scope.filterParams[filterKey] = newVal;
643                 }
644             }
645         });
646         // re-set filter params with valid values
647         this.applyFilterParamsToView(this.$scope.filterParams);
648
649     }
650
651     private changeFilterParams(changedFilterParams) {
652         const newParams = {};
653         Object.keys(changedFilterParams).forEach((k) => {
654             let newVal;
655             switch (k) {
656                 case 'components':
657                 case 'categories':
658                 case 'statuses':
659                     newVal = changedFilterParams[k] && changedFilterParams[k].length ? changedFilterParams[k].join(',') : null;
660                     break;
661                 case 'order':
662                     newVal = (changedFilterParams[k][1] ? '-' : '') + changedFilterParams[k][0];
663                     break;
664                 case 'term':
665                     newVal = changedFilterParams[k] ? changedFilterParams[k] : null;
666                     break;
667                 case 'active':
668                     newVal = changedFilterParams[k];
669                     break;
670                 default:
671                     return;
672             }
673             this.$scope.filterParams[k] = changedFilterParams[k];
674             newParams['filter.' + k] = newVal;
675         });
676         this.$state.go('.', newParams, {location: 'replace', notify: false}).then(() => {
677             this.applyFilterParamsToView(this.$scope.filterParams);
678         });
679     }
680 }