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=========================================================
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";
31 interface Checkboxes {
32 componentTypes:Array<string>;
33 resourceSubTypes:Array<string>;
36 interface CheckboxesFilter {
38 selectedComponentTypes:Array<string>;
39 selectedResourceSubTypes:Array<string>;
41 selectedCategoriesModel:Array<string>;
43 selectedStatuses:Array<Array<string>>;
48 onComponentSubTypesClick:Function;
49 onComponentTypeClick:Function;
50 onCategoryClick:Function;
51 onStatusClick:Function;
52 changeFilterTerm:Function;
55 interface IFilterParams {
59 order: [string, boolean];
64 interface ICategoriesMap {
66 category: ICategoryBase,
71 export interface ICatalogViewModelScope extends ng.IScope {
72 checkboxes:Checkboxes;
73 checkboxesFilter:CheckboxesFilter;
76 categories:Array<IMainCategory>;
77 confStatus:IConfigStatuses;
79 catalogFilterdItems:Array<Component>;
80 expandedSection:Array<string>;
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;
100 selectedCatalogItem: ICatalogSelector;
101 catalogSelectorItems: Array<ICatalogSelector>;
102 showCatalogSelector: boolean;
103 catalogAllItems:Array<Component>; /* fake data */
104 elementFoundTitle: string;
105 elementTypeTitle: string;
107 selectLeftSwitchItem (item: ICatalogSelector): void;
110 export class CatalogViewModel {
114 'Sdc.Services.EntityService',
120 'Sdc.Services.CacheService',
122 'ChangeLifecycleStateHandler',
127 private defaultFilterParams:IFilterParams = {
131 order: ['lastUpdateDate', true],
135 private categoriesMap:ICategoriesMap;
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
153 this.initLeftSwitch();
154 this.initScopeMembers();
155 this.loadFilterParams();
156 this.initCatalogData(); // Async task to get catalog from server.
157 this.initScopeMethods();
161 private initLeftSwitch = ():void => {
162 this.$scope.showCatalogSelector = false;
164 this.$scope.catalogSelectorItems = [
165 {value: CatalogSelectorTypes.Active, title: "Active Items", header: "Active"},
166 {value: CatalogSelectorTypes.Archive, title: "Archive", header: "Archived"}
168 // set active items is default
169 this.$scope.selectedCatalogItem = this.$scope.catalogSelectorItems[0];
172 private initCatalogData = ():void => {
173 if(this.$scope.selectedCatalogItem.value === CatalogSelectorTypes.Archive){
174 this.getArchiveCatalogItems();
176 this.getActiveCatalogItems();
180 private initScopeMembers = ():void => {
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;
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();
197 this.initCheckboxesFilter();
198 this.$scope.version = this.cacheService.get('version');
199 this.$scope.sortBy = 'lastUpdateDate';
200 this.$scope.reverse = true;
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 = [];
213 private initCategoriesMap(categoriesList?:(ICategoryBase)[], parentCategory:ICategoryBase=null): ICategoriesMap {
214 categoriesList = (categoriesList) ? categoriesList : this.$scope.categories;
216 // Init categories map
217 return categoriesList.reduce((acc, cat) => {
218 acc[cat.uniqueId] = {
220 parent: parentCategory
222 const catChildren = ((<IMainCategory>cat).subcategories)
223 ? (<IMainCategory>cat).subcategories
224 : (((<ISubCategory>cat).groupings)
225 ? (<ISubCategory>cat).groupings
228 Object.assign(acc, this.initCategoriesMap(catChildren, cat));
231 }, <ICategoriesMap>{});
234 private initScopeMethods = ():void => {
235 this.$scope.selectLeftSwitchItem = (item: ICatalogSelector): void => {
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);
244 case CatalogSelectorTypes.Archive:
245 this.getArchiveCatalogItems(true);
248 this.changeFilterParams({active: (item.value === CatalogSelectorTypes.Active)})
252 this.$scope.sectionClick = (section: string): void => {
253 let index: number = this.$scope.expandedSection.indexOf(section);
255 this.$scope.expandedSection.splice(index, 1);
257 this.$scope.expandedSection.push(section);
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']
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()});
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`;
284 return num + ` <b>${this.$scope.selectedCatalogItem.header}</b> Elements found`;
289 * Select | unselect sub resource when resource is clicked | unclicked.
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);
302 this.changeFilterParams({
303 components: components
308 * Selecting | unselect resources when sub resource is clicked | unclicked.
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 + '.'));
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);
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);
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);
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);
346 this.changeFilterParams({
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;
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);
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);
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);
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
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));
388 const parentCategoryIdx = categories.indexOf(parentCategory.uniqueId);
389 if (parentCategoryIdx !== -1) {
390 categories.splice(parentCategoryIdx, 1);
391 categories = categories.concat(additionalCategories);
394 prevParentCategory = parentCategory;
395 parentCategory = this.categoriesMap[parentCategory.uniqueId].parent;
401 this.changeFilterParams({
406 this.$scope.gui.onStatusClick = (statusKey: string, status: IConfigStatus, checked?: boolean) => {
407 const statuses = angular.copy(this.$scope.filterParams.statuses);
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);
418 this.changeFilterParams({
423 this.$scope.gui.changeFilterTerm = (filterTerm: string) => {
424 this.changeFilterParams({
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;
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));
445 else if ((<ISubCategory>category).groupings) {
446 catChildrenIds = (<ISubCategory>category).groupings.map((g) => g.uniqueId);
448 return catChildrenIds;
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;
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);
471 private applyFilterParamsToView(filterParams:IFilterParams) {
472 // reset checkboxes filter
473 this.initCheckboxesFilter();
475 this.applyFilterParamsComponents(filterParams);
476 this.applyFilterParamsCategories(filterParams);
477 this.applyFilterParamsStatuses(filterParams);
478 this.applyFilterParamsOrder(filterParams);
479 this.applyFilterParamsTerm(filterParams);
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);
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]);
497 } else { // sub component type
498 // init component sub types list
499 if (!componentSubTypesLists.hasOwnProperty(mainComp)) {
500 componentSubTypesLists[mainComp] = [];
502 // add sub type to list if not exist
503 if (componentSubTypesLists[mainComp].indexOf(subComp) === -1) {
504 componentSubTypesLists[mainComp].push(subComp);
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];
516 let selectedCatalogIndex = filterParams.active ? CatalogSelectorTypes.Active : CatalogSelectorTypes.Archive;
517 this.$scope.selectedCatalogItem = this.$scope.catalogSelectorItems[selectedCatalogIndex];
521 private applyFilterParamsCategories(filterParams:IFilterParams) {
522 this.$scope.checkboxesFilter.selectedCategoriesModel = filterParams.categories.reduce((acc, c) => {
524 const cat = this.categoriesMap[c].category;
526 acc = acc.concat(this.getAllCategoryChildrenIdsFlat(cat));
532 private getActiveCatalogItems(forceReload?: boolean): void {
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);
543 let onError = ():void => {
544 console.info('Failed to load catalog CatalogViewModel::getActiveCatalogItems');
545 this.$scope.gui.isLoading = false;
547 this.EntityService.getCatalog().then(onSuccess, onError);
549 let cachedComponents = this.cacheService.get('breadcrumbsComponents');
550 this.updateCatalogItems(cachedComponents);
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;
563 let onError = ():void => {
564 console.info('Failed to load catalog CatalogViewModel::getArchiveCatalogItems');
565 this.$scope.gui.isLoading = false;
568 this.ArchiveService.getArchiveCatalog().subscribe(onSuccess, onError);
570 let archiveCache = this.cacheService.get("archiveComponents");
571 this.updateCatalogItems(archiveCache);
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'));
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();
587 private isDefaultFilter = (): boolean => {
588 return angular.equals(this.defaultFilterParams, this.$scope.filterParams);
591 private applyFilterParamsStatuses(filterParams: IFilterParams) {
592 this.$scope.checkboxesFilter.selectedStatuses = filterParams.statuses.reduce((acc, stKey:string) => {
593 const status = this.$scope.confStatus[stKey];
595 acc.push(status.values);
601 private applyFilterParamsOrder(filterParams: IFilterParams) {
602 this.$scope.sortBy = filterParams.order[0];
603 this.$scope.reverse = filterParams.order[1];
606 private applyFilterParamsTerm(filterParams: IFilterParams) {
607 this.$scope.search = {
608 filterTerm: filterParams.term
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])) {
618 let filterKey = k.substr('filter.'.length);
620 case 'filter.components':
621 case 'filter.categories':
622 newVal = _.uniq(params[k].split(','));
623 newVal = this.cleanSubsFromList(newVal);
625 case 'filter.statuses':
626 newVal = _.uniq(params[k].split(','));
629 newVal = params[k].startsWith('-') ? [params[k].substr(1), true] : [params[k], false];
634 case 'filter.active':
635 newVal = (params[k] === "true" || params[k] === true);
638 // unknown filter key
642 this.$scope.filterParams[filterKey] = newVal;
646 // re-set filter params with valid values
647 this.applyFilterParamsToView(this.$scope.filterParams);
651 private changeFilterParams(changedFilterParams) {
652 const newParams = {};
653 Object.keys(changedFilterParams).forEach((k) => {
659 newVal = changedFilterParams[k] && changedFilterParams[k].length ? changedFilterParams[k].join(',') : null;
662 newVal = (changedFilterParams[k][1] ? '-' : '') + changedFilterParams[k][0];
665 newVal = changedFilterParams[k] ? changedFilterParams[k] : null;
668 newVal = changedFilterParams[k];
673 this.$scope.filterParams[k] = changedFilterParams[k];
674 newParams['filter.' + k] = newVal;
676 this.$state.go('.', newParams, {location: 'replace', notify: false}).then(() => {
677 this.applyFilterParamsToView(this.$scope.filterParams);