2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
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";
32 onComponentSubTypesClick:Function;
33 onComponentTypeClick:Function;
34 onCategoryClick:Function;
35 onStatusClick:Function;
36 changeFilterTerm:Function;
39 interface IFilterParams {
43 order: [string, boolean];
48 interface ICheckboxesFilterMap {
49 [key: string]: Array<string>;
53 interface ICheckboxesFilterKeys {
54 componentTypes: ICheckboxesFilterMap;
55 categories: ICheckboxesFilterMap;
56 statuses: ICheckboxesFilterMap;
59 interface ICategoriesMap {
61 category: ICategoryBase,
68 templateUrl: './catalog.component.html',
69 styleUrls:['./catalog.component.less']
71 export class CatalogComponent {
72 public checkboxesFilter:IEntityFilterObject;
73 public checkboxesFilterKeys:ICheckboxesFilterKeys;
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;
85 public reverse:boolean;
86 public filterParams:IFilterParams;
87 public search:ISearchFilter;
89 //this is for UI paging
90 public numberOfItemToDisplay:number;
92 public selectedCatalogItem: ICatalogSelector;
93 public catalogSelectorItems: Array<ICatalogSelector>;
94 public showCatalogSelector: boolean;
96 public typesChecklistModel: SdcUiCommon.ChecklistModel;
97 public categoriesChecklistModel: SdcUiCommon.ChecklistModel;
98 public statusChecklistModel: SdcUiCommon.ChecklistModel;
100 private defaultFilterParams:IFilterParams = {
104 order: ['lastUpdateDate', true],
108 private categoriesMap:ICategoriesMap;
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
122 this.initLeftSwitch();
123 this.initScopeMembers();
124 this.loadFilterParams();
125 this.initCatalogData(); // Async task to get catalog from server.
128 private initLeftSwitch = ():void => {
129 this.showCatalogSelector = false;
131 this.catalogSelectorItems = [
132 {value: CatalogSelectorTypes.Active, title: "Active Items", header: "Active"},
133 {value: CatalogSelectorTypes.Archive, title: "Archive", header: "Archived"}
135 // set active items is default
136 this.selectedCatalogItem = this.catalogSelectorItems[0];
139 private initCatalogData = ():void => {
140 if(this.selectedCatalogItem.value === CatalogSelectorTypes.Archive){
141 this.getArchiveCatalogItems();
143 this.getActiveCatalogItems();
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();
161 this.version = this.cacheService.get('version');
162 this.sortBy = 'lastUpdateDate';
166 private buildCheckboxLists() {
167 this.buildChecklistModelForTypes();
168 this.buildChecklistModelForCategories();
169 this.buildChecklistModelForStatuses();
172 private getTestIdForCheckboxByText = ( text: string ):string => {
173 return 'checkbox-' + text.toLowerCase().replace(/ /g, '');
176 private buildChecklistModelForTypes() {
177 this.componentTypes = {
178 Resource: ['VF', 'VFC', 'CR', 'PNF', 'CP', 'VL'],
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);
194 const testId = this.getTestIdForCheckboxByText(ct);
195 return new SdcUiCommon.ChecklistItemModel(ct, false, this.checkboxesFilterKeys.componentTypes._main.indexOf(ct) !== -1, subChecklist, testId, ct);
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))
211 return new SdcUiCommon.ChecklistItemModel(scat.name, false, this.checkboxesFilterKeys.categories[cat.uniqueId].indexOf(scat.uniqueId) !== -1, groupingsChecklistModel, this.getTestIdForCheckboxByText(scat.uniqueId), scat.uniqueId);
214 return new SdcUiCommon.ChecklistItemModel(cat.name, false, this.checkboxesFilterKeys.categories._main.indexOf(cat.uniqueId) !== -1, subCategoriesChecklistModel, this.getTestIdForCheckboxByText(cat.uniqueId), cat.uniqueId);
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,
225 this.checkboxesFilterKeys.statuses._main.indexOf(sKey) !== -1,
227 this.getTestIdForCheckboxByText(sKey),
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 = [];
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: [] };
249 private initCategoriesMap(categoriesList?:(ICategoryBase)[], parentCategory:ICategoryBase=null): ICategoriesMap {
250 categoriesList = (categoriesList) ? categoriesList : this.categories;
252 // Init categories map
253 return categoriesList.reduce((acc, cat) => {
254 acc[cat.uniqueId] = {
256 parent: parentCategory
258 const catChildren = ((<IMainCategory>cat).subcategories)
259 ? (<IMainCategory>cat).subcategories
260 : (((<ISubCategory>cat).groupings)
261 ? (<ISubCategory>cat).groupings
264 Object.assign(acc, this.initCategoriesMap(catChildren, cat));
267 }, <ICategoriesMap>{});
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);
278 case CatalogSelectorTypes.Archive:
279 this.getArchiveCatalogItems(true);
282 this.changeFilterParams({active: (item.value === CatalogSelectorTypes.Active)});
286 public sectionClick(section: string): void {
287 let index: number = this.expandedSection.indexOf(section);
289 this.expandedSection.splice(index, 1);
291 this.expandedSection.push(section);
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));
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']
317 public goToComponent(component: Component): void {
318 this.$state.go('workspace.general', {id: component.uniqueId, type: component.componentType.toLowerCase()});
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`;
329 return num + ` <b>${this.selectedCatalogItem.header}</b> Elements found`;
333 public initGui(): void {
337 * Select | unselect sub resource when resource is clicked | unclicked.
340 this.gui.onComponentTypeClick = (): void => {
341 this.changeFilterParams({
342 components: this.makeFilterParamsFromCheckboxes(this.typesChecklistModel)
346 this.gui.onCategoryClick = (): void => {
347 this.changeFilterParams({
348 categories: this.makeFilterParamsFromCheckboxes(this.categoriesChecklistModel)
352 this.gui.onStatusClick = (statusChecklistItem: SdcUiCommon.ChecklistItemModel) => {
353 this.changeFilterParams({
354 statuses: this.makeFilterParamsFromCheckboxes(this.statusChecklistModel)
358 this.gui.changeFilterTerm = (filterTerm: string) => {
359 this.changeFilterParams({
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;
374 this.numberOfItemToDisplay = Math.min(this.catalogFilteredItems.length, fullPagesAmount);
375 this.catalogFilteredSlicedItems = this.catalogFilteredItems.slice(0, this.numberOfItemToDisplay);
379 private isDefaultFilter = (): boolean => {
380 return angular.equals(this.defaultFilterParams, this.filterParams);
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();
388 private getActiveCatalogItems(forceReload?: boolean): void {
389 if (forceReload || this.componentShouldReload()) {
390 this.loaderService.activate();
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);
400 let onError = ():void => {
401 console.info('Failed to load catalog CatalogViewModel::getActiveCatalogItems');
402 this.loaderService.deactivate();
404 this.catalogService.getCatalog().subscribe(onSuccess, onError);
406 let cachedComponents = this.cacheService.get('breadcrumbsComponents');
407 this.updateCatalogItems(cachedComponents);
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);
420 let onError = ():void => {
421 console.info('Failed to load catalog CatalogViewModel::getArchiveCatalogItems');
422 this.loaderService.deactivate();
425 this.catalogService.getArchiveCatalog().subscribe(onSuccess, onError);
427 let archiveCache = this.cacheService.get("archiveComponents");
428 this.updateCatalogItems(archiveCache);
432 private updateCatalogItems = (items:Array<Component>):void => {
433 this.catalogItems = items;
434 this.catalogItems.forEach(this.addFilterTermToComponent);
435 this.filterCatalogItems();
438 private applyFilterParamsToView(filterParams:IFilterParams) {
439 // reset checkboxes filter
440 this.initCheckboxesFilter();
442 this.filterCatalogCategories();
444 this.applyFilterParamsComponents(filterParams);
445 this.applyFilterParamsCategories(filterParams);
446 this.applyFilterParamsStatuses(filterParams);
447 this.applyFilterParamsOrder(filterParams);
448 this.applyFilterParamsTerm(filterParams);
450 // do filters when filter params are changed:
451 this.filterCatalogItems();
454 private filterCatalogCategories() {
455 this.filteredCategories = this.makeFilteredCategories(this.categories, this.checkboxesFilter.selectedComponentTypes);
456 this.buildChecklistModelForCategories();
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);
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));
473 } else if ( chbox.subLevelChecklist ) {
474 this.applyFilterParamsToCheckboxes(chbox.subLevelChecklist, filterParamsList);
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));
488 let selectedCatalogIndex = filterParams.active ? CatalogSelectorTypes.Active : CatalogSelectorTypes.Archive;
489 this.selectedCatalogItem = this.catalogSelectorItems[selectedCatalogIndex];
492 private applyFilterParamsCategories(filterParams:IFilterParams) {
493 this.applyFilterParamsToCheckboxes(this.categoriesChecklistModel, filterParams.categories);
494 this.checkboxesFilter.selectedCategoriesModel = <Array<string>>_.flatMap(this.checkboxesFilterKeys.categories);
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], []);
502 private applyFilterParamsOrder(filterParams: IFilterParams) {
503 this.sortBy = filterParams.order[0];
504 this.reverse = filterParams.order[1];
507 private applyFilterParamsTerm(filterParams: IFilterParams) {
509 filterTerm: filterParams.term
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])) {
519 let paramsChecklist: SdcUiCommon.ChecklistModel = null;
520 let filterKey = k.substr('filter.'.length);
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;
529 // for those cases above - split param by comma and make reduced checklist values for filter params (url)
530 newVal = _.uniq(params[k].split(','));
533 newVal = params[k].startsWith('-') ? [params[k].substr(1), true] : [params[k], false];
538 case 'filter.active':
539 newVal = (params[k] === "true" || params[k] === true)? true : false;
542 // unknown filter key
546 this.filterParams[filterKey] = newVal;
550 // re-set filter params with valid values, and then re-build checklists
551 this.changeFilterParams(this.filterParams, true);
554 private changeFilterParams(changedFilterParams, rebuild:boolean = false) {
555 const newParams = {};
556 Object.keys(changedFilterParams).forEach((k) => {
562 newVal = changedFilterParams[k] && changedFilterParams[k].length ? changedFilterParams[k].join(',') : null;
565 newVal = (changedFilterParams[k][1] ? '-' : '') + changedFilterParams[k][0];
568 newVal = changedFilterParams[k] ? changedFilterParams[k] : null;
571 newVal = (changedFilterParams[k] === "true" || changedFilterParams[k] === true);
576 this.filterParams[k] = changedFilterParams[k];
577 newParams['filter.' + k] = newVal;
579 this.$state.go('.', newParams, {location: 'replace', notify: false}).then(() => {
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)
587 // rebuild the checkboxes to show selected
588 this.buildCheckboxLists();
591 this.applyFilterParamsToView(this.filterParams);
594 private makeFilteredCategories(categories:Array<IMainCategory>, selectedTypes:Array<string>=[]): Array<IMainCategory> {
595 let filteredCategories = categories.slice();
597 const filteredMainTypes = selectedTypes.reduce((acc, st) => {
598 const mainType = st.split('.')[0];
599 if (acc.indexOf(mainType) === -1) {
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'));
612 filteredCategories = _.intersectionBy(filteredCategories, filteredTypesCategories, c => c.uniqueId);
615 return filteredCategories;
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);
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);
628 if ('groupings' in cat && cat['groupings'] && cat['groupings'].length > 0) {
629 cat['groupings'] = this.makeSortedCategories(cat['groupings'], sortBy);
633 return sortedCategories;
636 private addFilterTermToComponent(component:Component) {
637 component.filterTerm = component.name + ' ' + component.description + ' ' + component.tags.toString() + ' ' + component.version;
638 component.filterTerm = component.filterTerm.toLowerCase();
641 private makeFilteredItems(catalogItems:Array<Component>, filter:IEntityFilterObject, search:ISearchFilter, sortBy:string, reverse:boolean) {
642 let filteredComponents:Array<Component> = catalogItems;
644 // common entity filter
645 // --------------------------------------------------------------------------
646 filter = Object.assign({ search }, filter); // add search to entity filter object
647 filteredComponents = EntityFilterPipe.transform(filteredComponents, filter);
650 // --------------------------------------------------------------------------
654 filteredComponents = _.sortBy(filteredComponents, cat => this.resourceNamePipe.transform(cat.name));
657 filteredComponents = _.sortBy(filteredComponents, [sortBy]);
660 _.reverse(filteredComponents);
664 return filteredComponents;