Support default value for category specific metadata
[sdc.git] / catalog-ui / src / app / view-models / workspace / tabs / general / general-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 {ModalsHandler, ValidationUtils, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ComponentType, DEFAULT_ICON,
24     ResourceType, ComponentState, instantiationType, ComponentFactory} from "app/utils";
25 import { EventListenerService, ProgressService} from "app/services";
26 import {CacheService, OnboardingService, ImportVSPService} from "app/services-ng2";
27 import {IAppConfigurtaion, IValidate, IMainCategory, Resource, ISubCategory,Service, ICsarComponent, Component, IMetadataKey} from "app/models";
28 import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model";
29 import {Dictionary} from "lodash";
30 import { PREVIOUS_CSAR_COMPONENT, CATEGORY_SERVICE_METADATA_KEYS } from "../../../../utils/constants";
31 import { Observable, Subject } from "rxjs";
32 import { MetadataEntry } from "app/models/metadataEntry";
33 import { Metadata } from "app/models/metadata";
34
35 export class Validation {
36     componentNameValidationPattern:RegExp;
37     contactIdValidationPattern:RegExp;
38     tagValidationPattern:RegExp;
39     VendorReleaseValidationPattern:RegExp;
40     VendorNameValidationPattern:RegExp;
41     VendorModelNumberValidationPattern:RegExp;
42     commentValidationPattern:RegExp;
43 }
44
45 export class componentCategories {//categories field bind to this obj in order to solve this bug: DE242059
46     selectedCategory:string;
47 }
48
49 export interface IEnvironmentContext {
50     defaultValue:string;
51     validValues:Array<string>;
52 }
53
54 export interface IGeneralScope extends IWorkspaceViewModelScope {
55     validation:Validation;
56     editForm:ng.IFormController;
57     categories:Array<IMainCategory>;
58     environmentContextObj:IEnvironmentContext;
59     latestCategoryId:string;
60     latestVendorName:string;
61     importedFileExtension:any;
62     isCreate:boolean;
63     isShowFileBrowse:boolean;
64     isShowOnboardingSelectionBrowse:boolean;
65     importedToscaBrowseFileText:string;
66     importCsarProProgressKey:string;
67     browseFileLabel:string;
68     componentCategories:componentCategories;
69     instantiationTypes:Array<instantiationType>;
70     isHiddenCategorySelected: boolean;
71
72     save():Promise<any>;
73     revert():void;
74     onImportFileChange():void;
75     validateField(field:any):boolean;
76     validateName(isInit:boolean):void;
77     calculateUnique(mainCategory:string, subCategory:string):string; // Build unique string from main and sub category
78     onVendorNameChange(oldVendorName:string):void;
79     convertCategoryStringToOneArray(category:string, subcategory:string):Array<IMainCategory>;
80     onCategoryChange():void;
81     onEcompGeneratedNamingChange():void;
82     openOnBoardingModal():void;
83     initCategoreis():void;
84     initEnvironmentContext():void;
85     initInstantiationTypes():void;
86     onInstantiationTypeChange():void;
87     updateIcon():void;
88     possibleToUpdateIcon():boolean;
89 }
90
91 // tslint:disable-next-line:max-classes-per-file
92 export class GeneralViewModel {
93
94     static '$inject' = [
95         '$scope',
96         'Sdc.Services.CacheService',
97         'ComponentNameValidationPattern',
98         'ContactIdValidationPattern',
99         'TagValidationPattern',
100         'VendorReleaseValidationPattern',
101         'VendorNameValidationPattern',
102         'VendorModelNumberValidationPattern',
103         'CommentValidationPattern',
104         'ValidationUtils',
105         'sdcConfig',
106         '$state',
107         'ModalsHandler',
108         'EventListenerService',
109         'Notification',
110         'Sdc.Services.ProgressService',
111         '$interval',
112         '$filter',
113         '$timeout',
114         'OnboardingService',
115         'ComponentFactory',
116         'ImportVSPService',
117         '$stateParams'
118     ];
119
120     constructor(private $scope:IGeneralScope,
121                 private cacheService:CacheService,
122                 private ComponentNameValidationPattern:RegExp,
123                 private ContactIdValidationPattern:RegExp,
124                 private TagValidationPattern:RegExp,
125                 private VendorReleaseValidationPattern:RegExp,
126                 private VendorNameValidationPattern:RegExp,
127                 private VendorModelNumberValidationPattern:RegExp,
128                 private CommentValidationPattern:RegExp,
129                 private ValidationUtils:ValidationUtils,
130                 private sdcConfig:IAppConfigurtaion,
131                 private $state:ng.ui.IStateService,
132                 private ModalsHandler:ModalsHandler,
133                 private EventListenerService:EventListenerService,
134                 private Notification:any,
135                 private progressService:ProgressService,
136                 protected $interval:any,
137                 private $filter:ng.IFilterService,
138                 private $timeout:ng.ITimeoutService,
139                 private onBoardingService: OnboardingService,
140                 private ComponentFactory:ComponentFactory,
141                 private importVSPService: ImportVSPService,
142                 private $stateParams: any) {
143
144         this.initScopeValidation();
145         this.initScopeMethods();
146         this.initScope();
147     }
148
149
150
151
152     private initScopeValidation = ():void => {
153         this.$scope.validation = new Validation();
154         this.$scope.validation.componentNameValidationPattern = this.ComponentNameValidationPattern;
155         this.$scope.validation.contactIdValidationPattern = this.ContactIdValidationPattern;
156         this.$scope.validation.tagValidationPattern = this.TagValidationPattern;
157         this.$scope.validation.VendorReleaseValidationPattern = this.VendorReleaseValidationPattern;
158         this.$scope.validation.VendorNameValidationPattern = this.VendorNameValidationPattern;
159         this.$scope.validation.VendorModelNumberValidationPattern = this.VendorModelNumberValidationPattern;
160         this.$scope.validation.commentValidationPattern = this.CommentValidationPattern;
161     };
162
163     private loadOnboardingFileCache = (): Observable<Dictionary<Dictionary<string>>> => {
164         let onboardCsarFilesMap:Dictionary<Dictionary<string>>;
165         let onSuccess = (vsps:Array<ICsarComponent>) => {
166             onboardCsarFilesMap = {};
167             _.each(vsps, (vsp:ICsarComponent)=>{
168                 onboardCsarFilesMap[vsp.packageId] = onboardCsarFilesMap[vsp.packageId] || {};
169                 onboardCsarFilesMap[vsp.packageId][vsp.version] = vsp.vspName + " (" + vsp.version + ")";
170             });
171             this.cacheService.set('onboardCsarFilesMap', onboardCsarFilesMap);
172             return onboardCsarFilesMap;
173         };
174         let onError = (): void =>{
175             console.log("Error getting onboarding list");
176         };
177         return this.onBoardingService.getOnboardingVSPs().map(onSuccess, onError);
178     };
179
180     private setImportedFileText = ():void => {
181
182         if(!this.$scope.isShowOnboardingSelectionBrowse) return;
183
184         //these variables makes it easier to read this logic
185         let csarUUID:string = (<Resource>this.$scope.component).csarUUID;
186         let csarVersion:string = (<Resource>this.$scope.component).csarVersion;
187
188         let onboardCsarFilesMap:Dictionary<Dictionary<string>> = this.cacheService.get('onboardCsarFilesMap');
189         let assignFileName = ():void => {
190             if(this.$scope.component.vspArchived){
191                 this.$scope.importedToscaBrowseFileText = 'VSP is archived';
192             } else {
193                 if(this.$stateParams.componentCsar && this.$scope.component.lifecycleState === 'NOT_CERTIFIED_CHECKIN' && !this.$scope.isCreateMode()) {
194                     this.$scope.importedToscaBrowseFileText = this.$scope.originComponent.name + ' (' + (this.$scope.originComponent as Resource).csarVersion + ')';
195                 } else {
196                     this.$scope.importedToscaBrowseFileText = onboardCsarFilesMap[csarUUID][csarVersion];
197                 }
198             }
199         }
200
201
202         if(this.$scope.component.vspArchived || (onboardCsarFilesMap && onboardCsarFilesMap[csarUUID] && onboardCsarFilesMap[csarUUID][csarVersion])){ //check that the file name is already in cache
203             assignFileName();
204         } else {
205             this.loadOnboardingFileCache().subscribe((onboardingFiles) => {
206                 onboardCsarFilesMap = onboardingFiles;
207                 this.cacheService.set('onboardCsarFilesMap', onboardingFiles);
208                 assignFileName();
209             }, ()=> {});
210         }
211
212     }
213
214     isCreateModeAvailable(verifyObj:string): boolean {
215         var isCheckout:boolean = ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState;
216         return this.$scope.isCreateMode() || (isCheckout && !verifyObj)
217     }
218
219     private initScope = ():void => {
220
221         this.$scope.importCsarProgressKey = "importCsarProgressKey";
222
223         this.$scope.browseFileLabel = (this.$scope.component.isResource() && ((<Resource>this.$scope.component).resourceType === ResourceType.VF || (<Resource>this.$scope.component).resourceType === 'SRVC')) ||  this.$scope.component.isService() ? 'Upload File:' : 'Upload VFC:';
224         this.$scope.progressService = this.progressService;
225         this.$scope.componentCategories = new componentCategories();
226         this.$scope.componentCategories.selectedCategory = this.$scope.component.selectedCategory;
227
228         // Init UIModel
229         this.$scope.component.tags = _.without(this.$scope.component.tags, this.$scope.component.name);
230
231         // Init categories
232         this.$scope.initCategoreis();
233
234         // Init Environment Context
235         this.$scope.initEnvironmentContext();
236
237         // Init the decision if to show file browse.
238         this.$scope.isShowFileBrowse = false;
239         if (this.$scope.component.isResource()) {
240             let resource:Resource = <Resource>this.$scope.component;
241             console.log(resource.name + ": " + resource.csarUUID);
242             if (resource.importedFile) { // Component has imported file.
243                 this.$scope.isShowFileBrowse = true;
244             }
245             if (resource.resourceType === ResourceType.VF && !resource.csarUUID) {
246                 this.$scope.isShowFileBrowse = true;
247             }
248         } else if(this.$scope.component.isService()){
249             let service: Service = <Service>this.$scope.component;
250             console.log(service.name + ": " + service.csarUUID);
251             if (service.importedFile) { // Component has imported file.
252                 this.$scope.isShowFileBrowse = true;
253                 (<Service>this.$scope.component).serviceType = 'Service';
254             }
255             if (this.$scope.isEditMode() && service.serviceType == 'Service' && !service.csarUUID) {
256                 this.$scope.isShowFileBrowse = true;
257             }
258             // Init Instantiation types
259             this.$scope.initInstantiationTypes();
260         }
261
262         if (this.cacheService.get(PREVIOUS_CSAR_COMPONENT)) { //keep the old component in the cache until checkout, so we dont need to pass it around
263             this.$scope.setOriginComponent(this.cacheService.get(PREVIOUS_CSAR_COMPONENT));
264             this.cacheService.remove(PREVIOUS_CSAR_COMPONENT);
265         }
266
267         if (this.$stateParams.componentCsar && !this.$scope.isCreateMode()) {
268             this.$scope.updateUnsavedFileFlag(true);
269             // We are coming from update VSP modal we need to automatically checkout (if needed) and save the VF
270             if (this.$scope.component.lifecycleState !== ComponentState.NOT_CERTIFIED_CHECKOUT) {
271                 // Checkout is needed after that a save will be invoked in workspace-view.handleLifeCycleStateChange
272                 this.EventListenerService.notifyObservers(EVENTS.ON_LIFECYCLE_CHANGE_WITH_SAVE, 'checkOut');
273                 // if(this.$scope.component.lifecycleState !== 'NOT_CERTIFIED_CHECKIN') {
274                 //     (<Resource>this.$scope.component).csarVersion = this.$stateParams.componentCsar.csarVersion;
275                 // }
276             } else {
277                 this.$scope.save();
278             }
279         }
280
281
282         if (this.$scope.component.isResource() &&
283             (this.$scope.component as Resource).resourceType === ResourceType.VF ||
284                 (this.$scope.component as Resource).resourceType === ResourceType.PNF && (this.$scope.component as Resource).csarUUID) {
285             this.$scope.isShowOnboardingSelectionBrowse = true;
286             this.setImportedFileText();
287         } else {
288             this.$scope.isShowOnboardingSelectionBrowse = false;
289         }
290
291
292         //init file extensions based on the file that was imported.
293         if (this.$scope.component.isResource() && (<Resource>this.$scope.component).importedFile) {
294             let fileName:string = (<Resource>this.$scope.component).importedFile.filename;
295             let fileExtension:string = fileName.split(".").pop();
296             if (this.sdcConfig.csarFileExtension.indexOf(fileExtension.toLowerCase()) !== -1) {
297                 this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension;
298                 (<Resource>this.$scope.component).importedFile.filetype = "csar";
299             } else if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1) {
300                 (<Resource>this.$scope.component).importedFile.filetype = "yaml";
301                 this.$scope.importedFileExtension = this.sdcConfig.toscaFileExtension;
302             }
303             this.$scope.restoreFile = angular.copy((<Resource>this.$scope.originComponent).importedFile); //create backup
304         } else if (this.$scope.isEditMode() && (<Resource>this.$scope.component).resourceType === ResourceType.VF) {
305             this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension;
306             //(<Resource>this.$scope.component).importedFile.filetype="csar";
307         }
308
309
310
311         this.$scope.setValidState(true);
312
313         this.$scope.calculateUnique = (mainCategory:string, subCategory:string):string => {
314             let uniqueId:string = mainCategory;
315             if (subCategory) {
316                 uniqueId += "_#_" + subCategory; // Set the select category combobox to show the selected category.
317             }
318             return uniqueId;
319         };
320
321         //TODO remove this after handling contact in UI
322         if (this.$scope.isCreateMode()) {
323             this.$scope.component.contactId = this.cacheService.get("user").userId;
324             this.$scope.originComponent.contactId = this.$scope.component.contactId;
325         }
326
327
328         this.$scope.$on('$destroy', () => {
329             this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE_WITH_SAVE);
330             this.EventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
331         });
332
333     };
334
335     // Convert category string MainCategory_#_SubCategory to Array with one item (like the server except)
336     private convertCategoryStringToOneArray = ():IMainCategory[] => {
337         let tmp = this.$scope.component.selectedCategory.split("_#_");
338         let mainCategory = tmp[0];
339         let subCategory = tmp[1];
340
341         // Find the selected category and add the relevant sub category.
342         let selectedMainCategory:IMainCategory = <IMainCategory>_.find(this.$scope.categories, function (item) {
343             return item["name"] === mainCategory;
344
345         });
346
347         let mainCategoryClone = angular.copy(selectedMainCategory);
348         if (subCategory) {
349             let selectedSubcategory = <ISubCategory>_.find(selectedMainCategory.subcategories, function (item) {
350                 return item["name"] === subCategory;
351             });
352             mainCategoryClone['subcategories'] = [angular.copy(selectedSubcategory)];
353         }
354         let tmpSelected = <IMainCategory> mainCategoryClone;
355
356         let result:IMainCategory[] = [];
357         result.push(tmpSelected);
358
359         return result;
360     };
361
362     private updateComponentNameInBreadcrumbs = ():void => {
363         // update breadcrum after changing name
364         this.$scope.breadcrumbsModel[1].updateSelectedMenuItemText(this.$scope.component.getComponentSubType() + ': ' + this.$scope.component.name);
365         this.$scope.updateMenuComponentName(this.$scope.component.name);
366     };
367
368     //Find if a category is applicable for External API or not
369     private isHiddenCategory = (category: string) => {
370         let items: Array<any> = new Array<any>();
371         items = this.$scope.sdcMenu.component_workspace_menu_option[this.$scope.component.getComponentSubType()];
372         for(let index = 0; index < items.length; ++index) {
373             if ((items[index].hiddenCategories && items[index].hiddenCategories.indexOf(category) > -1)) {
374                 return true;
375             }
376         }
377         return false;
378     };
379
380     private filteredCategories = () => {
381         let tempCategories: Array<IMainCategory> = new Array<IMainCategory>();
382         this.$scope.categories.forEach((category) => {
383             if (!this.isHiddenCategory(category.name)
384                 && this.$scope.isCreateMode()
385             ) {
386                 tempCategories.push(category);
387             } else if ((ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState)
388                 && !this.isHiddenCategory(this.$scope.component.selectedCategory)
389                 && !this.isHiddenCategory(category.name)
390             ) {
391                 tempCategories.push(category);
392             } else if ((ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState)
393                 && this.isHiddenCategory(this.$scope.component.selectedCategory)) {
394                 tempCategories.push(category);
395             }
396         });
397
398         return tempCategories;
399     };    
400    
401     private initScopeMethods = ():void => {
402
403         this.$scope.initCategoreis = ():void => {
404             if (this.$scope.componentType === ComponentType.RESOURCE) {
405                 this.$scope.categories = this.cacheService.get('resourceCategories');
406
407             }
408             if (this.$scope.componentType === ComponentType.SERVICE) {
409                 this.$scope.categories = this.cacheService.get('serviceCategories');
410
411                 //Remove categories from dropdown applicable for External API
412                 if (this.$scope.isCreateMode() || (ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState)) {
413                     this.$scope.categories = this.filteredCategories();
414                     //Flag to disbale category if service is created through External API
415                     this.$scope.isHiddenCategorySelected = this.isHiddenCategory(this.$scope.component.selectedCategory);
416                 }
417                 
418             }
419         };
420
421         this.$scope.initInstantiationTypes = ():void => {
422             if (this.$scope.componentType === ComponentType.SERVICE) {
423                 this.$scope.instantiationTypes = new Array();
424                 this.$scope.instantiationTypes.push(instantiationType.A_LA_CARTE);
425                 this.$scope.instantiationTypes.push(instantiationType.MACRO);
426                 var instantiationTypeField:string =(<Service>this.$scope.component).instantiationType;
427                 if (instantiationTypeField === ""){
428                     this.$scope.instantiationTypes.push("");
429                 }
430                 else if (this.isCreateModeAvailable(instantiationTypeField)) {
431                     (<Service>this.$scope.component).instantiationType = instantiationType.A_LA_CARTE;
432
433                 }
434             }
435         };
436
437         this.$scope.initEnvironmentContext = ():void => {
438             if (this.$scope.componentType === ComponentType.SERVICE) {
439                 this.$scope.environmentContextObj = this.cacheService.get('UIConfiguration').environmentContext;
440                 var environmentContext:string =(<Service>this.$scope.component).environmentContext;
441                 // In creation new service OR check outing old service without environmentContext parameter - set default value
442                 if(this.isCreateModeAvailable(environmentContext)){
443                     (<Service>this.$scope.component).environmentContext = this.$scope.environmentContextObj.defaultValue;
444                 }
445             }
446         };
447
448         this.$scope.validateField = (field:any):boolean => {
449             if (field && field.$dirty && field.$invalid) {
450                 return true;
451             }
452             return false;
453         };
454
455         this.$scope.openOnBoardingModal = ():void => {
456             if(this.$scope.component.vspArchived) return;
457             let csarUUID = (<Resource>this.$scope.component).csarUUID;
458             let csarVersion = (<Resource>this.$scope.component).csarVersion;
459             this.importVSPService.openOnboardingModal(csarUUID, csarVersion).subscribe((result) => {
460                 this.ComponentFactory.getComponentWithMetadataFromServer(result.type.toUpperCase(), result.previousComponent.uniqueId).then(
461                     (component:Component)=> {
462                     if (result.componentCsar && component.isResource()){
463                         this.cacheService.set(PREVIOUS_CSAR_COMPONENT, angular.copy(component));
464                         component = this.ComponentFactory.updateComponentFromCsar(result.componentCsar, <Resource>component);
465                     }
466                     this.$scope.setComponent(component);
467                     this.$scope.save();
468                     this.setImportedFileText();
469                 }, ()=> {
470                     // ERROR
471                 });
472             })
473         };
474
475         this.$scope.updateIcon = ():void => {
476             this.ModalsHandler.openUpdateIconModal(this.$scope.component).then((isDirty:boolean)=> {
477                 if(isDirty && !this.$scope.isCreateMode()){
478                     this.setUnsavedChanges(true);
479                 }
480             }, ()=> {
481                 // ERROR
482             });
483         };
484
485         this.$scope.possibleToUpdateIcon = ():boolean => {
486             if(this.$scope.componentCategories.selectedCategory && (!this.$scope.component.isResource() || this.$scope.component.vendorName) && !this.$scope.component.isAlreadyCertified()){
487                 return true;
488             }else{
489                 return false;
490             }
491         }
492
493         this.$scope.validateName = (isInit:boolean):void => {
494             if (isInit === undefined) {
495                 isInit = false;
496             }
497
498             let name = this.$scope.component.name;
499             if (!name || name === "") {
500                 if (this.$scope.editForm
501                     && this.$scope.editForm["componentName"]
502                     && this.$scope.editForm["componentName"].$error) {
503
504                     // Clear the error name already exists
505                     this.$scope.editForm["componentName"].$setValidity('nameExist', true);
506                 }
507
508                 return;
509             }
510             
511             let subtype:string = ComponentType.RESOURCE == this.$scope.componentType ? this.$scope.component.getComponentSubType() : undefined;
512             if (subtype == "SRVC") {
513                 subtype = "VF"
514             }
515
516             const onFailed = (response) => {
517                 // console.info('onFaild', response);
518                 // this.$scope.isLoading = false;
519             };
520
521             const onSuccess = (validation:IValidate) => {
522                 this.$scope.editForm['componentName'].$setValidity('nameExist', validation.isValid);
523                 if (validation.isValid) {
524                     // update breadcrumb after changing name
525                     this.updateComponentNameInBreadcrumbs();
526                 }
527             };
528
529             if (isInit) {
530                 // When page is init after update
531                 if (this.$scope.component.name !== this.$scope.originComponent.name) {
532                     if (!(this.$scope.componentType === ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID !== undefined)
533                     ) {
534                         this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
535                     }
536                 }
537             } else {
538                 // Validating on change (has debounce)
539                 if (this.$scope.editForm
540                     && this.$scope.editForm["componentName"]
541                     && this.$scope.editForm["componentName"].$error
542                     && !this.$scope.editForm["componentName"].$error.pattern
543                     && (!this.$scope.originComponent.name || this.$scope.component.name.toUpperCase() !== this.$scope.originComponent.name.toUpperCase())
544                 ) {
545                     if (!(this.$scope.componentType === ComponentType.RESOURCE && (this.$scope.component as Resource).csarUUID !== undefined)
546                     ) {
547                         this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
548                     }
549                 } else if (this.$scope.editForm && this.$scope.originComponent.name && this.$scope.component.name.toUpperCase() === this.$scope.originComponent.name.toUpperCase()) {
550                     // Clear the error
551                     this.$scope.editForm['componentName'].$setValidity('nameExist', true);
552                 }
553             }
554         };
555
556
557         this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE_WITH_SAVE, (nextState) => {
558             if (this.$state.current.data.unsavedChanges && this.$scope.isValidForm) {
559                 this.$scope.save().then(() => {
560                     this.$scope.handleChangeLifecycleState(nextState);
561                 }, () => {
562                     console.error('Save failed, unable to change lifecycle state to ' + nextState);
563                 });
564             } else if(!this.$scope.isValidForm){
565                 console.error('Form is not valid');
566             } else {
567                 let newCsarVersion:string;
568                 if(this.$scope.unsavedFile) {
569                     newCsarVersion = (this.$scope.component as Resource).csarVersion;
570                 }
571                 if(this.$stateParams.componentCsar && !this.$scope.isCreateMode()) {
572                     const onError = (): void => {
573                         if (this.$scope.component.lifecycleState === 'NOT_CERTIFIED_CHECKIN') {
574                             this.$scope.revert();
575                         }
576                     };
577                     this.$scope.handleChangeLifecycleState(nextState, newCsarVersion, onError);
578
579                 } else {
580                     this.$scope.handleChangeLifecycleState(nextState, newCsarVersion);
581                 }
582             }
583         });
584
585         this.$scope.revert = ():void => {
586             // in state of import file leave the file in place
587
588             this.$scope.setComponent(this.ComponentFactory.createComponent(this.$scope.originComponent));
589
590             if (this.$scope.component.isResource() && this.$scope.restoreFile) {
591                 (this.$scope.component as Resource).importedFile = angular.copy(this.$scope.restoreFile);
592             }
593
594             this.setImportedFileText();
595             this.$scope.updateBreadcrumbs(this.$scope.component); // update on workspace
596
597             this.$scope.componentCategories.selectedCategory = this.$scope.originComponent.selectedCategory;
598             this.setUnsavedChanges(false);
599             this.$scope.updateUnsavedFileFlag(false);
600             this.$scope.editForm.$setPristine();
601         };
602
603         this.$scope.onImportFileChange = () => {
604
605             if( !this.$scope.restoreFile && this.$scope.editForm.fileElement.value && this.$scope.editForm.fileElement.value.filename || // if file started empty but we have added a new one
606                 this.$scope.restoreFile && !angular.equals(this.$scope.restoreFile, this.$scope.editForm.fileElement.value)){ // or file was swapped for a new one
607                 this.$scope.updateUnsavedFileFlag(true);
608             } else {
609                 this.$scope.updateUnsavedFileFlag(false);
610                 this.$scope.editForm.fileElement.$setPristine();
611             }
612         };
613
614         this.$scope.$watchCollection('component.name', (newData: any): void => {
615             this.$scope.validateName(false);
616         });
617
618         // Notify the parent if this step valid or not.
619         this.$scope.$watch('editForm.$valid', (newVal, oldVal) => {
620             this.$scope.setValidState(newVal);
621         });
622
623         this.$scope.$watch('editForm.$dirty', (newVal, oldVal) => {
624             if (newVal && !this.$scope.isCreateMode()) {
625                 this.setUnsavedChanges(true);
626             }
627
628         });
629
630         this.$scope.onCategoryChange = (): void => {
631             this.$scope.component.selectedCategory = this.$scope.componentCategories.selectedCategory;
632             this.$scope.component.categories = this.convertCategoryStringToOneArray();
633             this.$scope.component.icon = DEFAULT_ICON;
634             if (this.$scope.component.categories[0].metadataKeys) {
635                 for (let metadataKey of this.$scope.component.categories[0].metadataKeys) {
636                     if (!this.$scope.component.categorySpecificMetadata[metadataKey.name]) {
637                         this.$scope.component.categorySpecificMetadata[metadataKey.name] = metadataKey.defaultValue ? metadataKey.defaultValue : "";
638                    }
639                 }
640             }
641             if (this.$scope.component.categories[0].subcategories && this.$scope.component.categories[0].subcategories[0].metadataKeys) {
642                 for (let metadataKey of this.$scope.component.categories[0].subcategories[0].metadataKeys) {
643                     if (!this.$scope.component.categorySpecificMetadata[metadataKey.name]) {
644                         this.$scope.component.categorySpecificMetadata[metadataKey.name] = metadataKey.defaultValue ? metadataKey.defaultValue : "";
645                    }
646                 }
647             }
648         };
649
650         this.$scope.onEcompGeneratedNamingChange = (): void => {
651             if (!(this.$scope.component as Service).ecompGeneratedNaming) {
652                 (this.$scope.component as Service).namingPolicy = '';
653             }
654         };
655
656         this.$scope.onVendorNameChange = (oldVendorName: string): void => {
657             if (this.$scope.component.icon === oldVendorName) {
658                 this.$scope.component.icon = DEFAULT_ICON;
659             }
660         };
661         this.EventListenerService.registerObserverCallback(EVENTS.ON_LIFECYCLE_CHANGE, this.$scope.reload);
662
663
664         this.$scope.isMetadataKeyMandatory = (key: string): boolean => {
665             let metadataKey = this.getMetadataKey(this.$scope.component.categories, key);
666             return metadataKey && metadataKey.mandatory;
667         }
668
669         this.$scope.getMetadataKeyValidValues = (key: string): string[] => {
670             let metadataKey = this.getMetadataKey(this.$scope.component.categories, key);
671             if (metadataKey) {
672                 return metadataKey.validValues;
673             }
674             return [];  
675         }
676
677         this.$scope.isMetadataKeyForComponentCategory = (key: string): boolean => {
678             return this.getMetadataKey(this.$scope.component.categories, key) != null;
679         }
680
681         this.$scope.isCategoryServiceMetadataKey = (key: string): boolean => {
682             return this.isServiceMetadataKey(key);
683         }
684
685         this.$scope.isMetadataKeyForComponentCategoryService = (key: string, attribute: string): boolean => {
686             let metadatakey = this.getMetadataKey(this.$scope.component.categories, key);
687             if (metadatakey && (!this.$scope.component[attribute] || !metadatakey.validValues.find(v => v === this.$scope.component[attribute]))) {
688                 this.$scope.component[attribute] = metadatakey.defaultValue;
689             }
690             return metadatakey != null;
691          }
692     }
693
694     private setUnsavedChanges = (hasChanges: boolean): void => {
695         this.$state.current.data.unsavedChanges = hasChanges;
696     }
697
698     private getMetadataKey(categories: IMainCategory[], key: string) : IMetadataKey {
699         let metadataKey = this.getSubcategoryMetadataKey(this.$scope.component.categories, key);
700         if (!metadataKey){
701             return this.getCategoryMetadataKey(this.$scope.component.categories, key);
702         }
703         return metadataKey;
704     }
705
706     private getSubcategoryMetadataKey(categories: IMainCategory[], key: string) : IMetadataKey {
707             if (categories[0].subcategories && categories[0].subcategories[0].metadataKeys && categories[0].subcategories[0].metadataKeys.some(metadataKey => metadataKey.name == key)) {
708             return categories[0].subcategories[0].metadataKeys.find(metadataKey => metadataKey.name == key);
709         }
710         return null;
711     }
712
713     private getCategoryMetadataKey(categories: IMainCategory[], key: string) : IMetadataKey {
714             if (categories[0].metadataKeys && categories[0].metadataKeys.some(metadataKey => metadataKey.name == key)) {
715             return categories[0].metadataKeys.find(metadataKey => metadataKey.name == key);
716         }
717         return null;
718     }
719
720     private isServiceMetadataKey(key: string) : boolean {
721         return CATEGORY_SERVICE_METADATA_KEYS.indexOf(key) > -1;
722     }
723
724 }
725