diff options
Diffstat (limited to 'catalog-ui/app/scripts/view-models/wizard')
32 files changed, 4119 insertions, 0 deletions
diff --git a/catalog-ui/app/scripts/view-models/wizard/ReadMe.txt b/catalog-ui/app/scripts/view-models/wizard/ReadMe.txt new file mode 100644 index 0000000000..ca5cfb988a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/ReadMe.txt @@ -0,0 +1,54 @@ +import-asset wizard +======================================================== + +What should be done in each wizard step: +---------------------------------------- + +1. Each step scope should extend IAssetCreationStepScope (this way you can call methods on the wizard scope). + Example: + export interface IGeneralStepScope extends IAssetCreationStepScope { + + } + +2. Each step class should implements IAssetCreationStep + Example: export class GeneralStepViewModel implements IAssetCreationStep { + + } + +3. Add the method: public save = (callback:Function):void => {} + The method should perform the save and call: callback(true); in case of success, or callback(false); in case of error. + Example: + var onSuccess:Function = (resourceProperties:Services.IResourceResource) => { + this.$scope.setAngularResourceOfResource(resourceProperties); + var resourceObj = new Sdc.Models.Resource(resourceProperties); + this.$scope.setEntity(resourceObj); + this.$scope.latestEntityName = (resourceProperties.resourceName); + callback(true); + }; + +4. Add the first line after the constructor: this.$scope.registerChild(this); + This will register the current step reference in the wizard. + +5. Each step can get and set angular $resource of resource from the wizard. + // Will be called from each step to get current entity. + this.$scope.getAngularResourceOfResource = ():Services.IResourceResource => { + return this.resourceProperties; + }; + + // Will be called from each step after save to update the resource. + this.$scope.setAngularResourceOfResource = (resourceProperties:Services.IResourceResource):void => { + this.resourceProperties = resourceProperties; + this.fillAssetNameAndType(); + }; + + Note: after success save, set setAngularResourceOfResource in the wizard (see example in step 3). + +6. The wizard needs to know if the step is valid (to know if to show next button), I used the following to update the wizard: + this.$scope.$watch("editForm.$valid", function(newVal, oldVal){ + this.$scope.setValidState(newVal); + }); + + Note: in case there is no save for the step, and the step is always valid, call: this.$scope.setValidState(true); + + + diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html new file mode 100644 index 0000000000..97817d59f2 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html @@ -0,0 +1,137 @@ +<div class="artifact-deployment-step" data-ng-controller="Sdc.ViewModels.Wizard.ArtifactDeploymentStepViewModel"> + + <div data-tests-id="add-deployment-artifact-button" data-tests-id="addGrey" class="w-sdc-classic-btn gray" data-ng-click="addOrUpdate({})">Add</div> + + <div class="table-container-flex"> + + <div class="table"> + + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item"></div> + </div> + + <form class="body" name="editForm"> + + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + + <!-- Artifact row --> + <div ng-if="noArtifactsToShow()" class="no-row-text" translate="DEPLOYMENT_ARTIFACT_NO_ARTIFACTS_TO_DISPLAY"></div> + <div data-ng-repeat-start="artifact in artifacts | orderBy:sortBy:reverse track by $index" + class="flex-container data-row" + data-ng-class="{'selected': artifact.selected || undefined==artifact.selected && updateInProgress}" + data-ng-if="artifact.esId"> + + <div class="table-col-general flex-item" data-ng-click="update(artifact)"> + <loader data-display="isLoading"></loader> + <span class="sprite table-arrow" data-tests-id="{{artifact.artifactDisplayName}}" data-ng-class="{'opened': artifact.selected || undefined==artifact.selected && updateInProgress}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-tests-id="{{artifact.artifactType}}"> + {{artifact.artifactType}} + </div> + <div class="table-col-general flex-item" data-tests-id="{{artifact.timeout}}"> + {{artifact.timeout? artifact.timeout:''}} + </div> + + <div class="table-btn-col flex-item" sdc-keyboard-events> + <button class="table-edit-btn" data-ng-click="addOrUpdate(artifact)"></button> + <button class="table-delete-btn" data-ng-click="delete(artifact)"> </button> + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="artifact.selected || undefined==artifact.selected && updateInProgress" class="w-sdc-form item-opened"> + <!-- Artifact panel opened --> + + <!-- Description field --> + <div class="w-sdc-form-item" ng-form="descriptionForm" data-ng-class="{error:(descriptionForm.$dirty && descriptionForm.$invalid)}"> + <label class="i-sdc-env-form-label required">Description</label> + <textarea class="i-sdc-form-textarea {{$index}}" + data-ng-maxlength="256" + maxlength="256" + data-ng-required="true" + name="description" + data-ng-model="artifact.description" + data-ng-model-options="{ debounce: 200 }" + data-ng-pattern="getValidationPattern('string')" + data-tests-id="description"> + </textarea> + + <div class="input-error" data-ng-show="descriptionForm.$dirty && descriptionForm.$invalid"> + <span ng-show="descriptionForm.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span> + <span ng-show="descriptionForm.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span> + <span ng-show="descriptionForm.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + + </div> + + <!-- Parameters in 2 columns --> + <div class="w-sdc-form-columns-wrapper" data-ng-if="artifact.heatParameters"> + <!-- Left column --> + <div class="w-sdc-form-column"> + <div class="i-sdc-form-item" ng-form="parameterForm" data-ng-repeat="parameter in artifact.heatParameters.slice(0, artifact.heatParameters.length%2+artifact.heatParameters.length/2) | orderBy: 'name' track by $index"> + <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label> + <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span> + <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid)}" + data-ng-model-options="{ debounce: 200 }" + data-ng-model="parameter.currentValue" + type="text" + name="currentValue" + data-ng-pattern="getValidationPattern(parameter.type, 'heat')" + data-ng-required="parameter.defaultValue" + data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))" + data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)" + /> + + <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid"> + <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span> + </div> + </div> + </div> + + <!-- Right column --> + <div class="w-sdc-form-column"> + <div class="i-sdc-form-item" ng-form="parameterForm" data-ng-repeat="parameter in artifact.heatParameters.slice(artifact.heatParameters.length%2+artifact.heatParameters.length/2) | orderBy: 'name' track by $index"> + <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label> + <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span> + <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid)}" + data-ng-model-options="{ debounce: 200 }" + data-ng-model="parameter.currentValue" + type="text" + name="currentValue" + data-ng-pattern="getValidationPattern(parameter.type, 'heat')" + data-ng-required="parameter.defaultValue" + data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))" + data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)" + /> + + <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid"> + <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span> + </div> + </div> + </div> + + </div><!-- Close: Parameters in 2 columns --> + </div><!-- Close: Artifact panel opened --> + + <!-- Add artifacts buttons --> + <button class="add-button" data-ng-repeat="artifact in artifacts track by $index" + data-ng-show="!artifact.esId" + data-tests-id="{{artifact.artifactDisplayName}}" + translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT" + translate-values="{'name': '{{artifact.artifactDisplayName}}'}" + data-ng-click="addOrUpdate(artifact)"></button> + + <!-- Top add button --> + <button class="add-button" translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER" data-ng-click="addOrUpdate({})"></button> + </perfect-scrollbar> + </form> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less new file mode 100644 index 0000000000..043fba3277 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less @@ -0,0 +1,107 @@ +.artifact-deployment-step { + + .table-container-flex .table .body .data-row + div.item-opened { + padding: 10px 40px 10px 30px; + } + + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table { + height:412px; + margin-bottom: 0; + } + + .parameter-description { + .circle(18px, @color_p); + content: '?'; + line-height: 18px; + vertical-align: middle; + margin-left: 5px; + cursor: default; + display: inline-block; + position: absolute; + top: 16px; + } + + .table-container-flex { + + margin-top: 0px; + + .flex-item:nth-child(1) { + flex-grow: 15; + padding: 5px 4px; + .hand; + span.table-arrow { + margin-right: 7px; + + } + } + + .flex-item:nth-child(2) { + padding: 5px 4px; + flex-grow: 6; + } + + .flex-item:nth-child(3) { + padding: 5px 4px; + flex-grow: 9; + } + + .flex-item:nth-child(4) { + padding: 5px 4px; + flex-grow: 3; + padding-top: 10px; + } + } + .w-sdc-form{ + text-align: left; + + .w-sdc-env-params{ + border-top: 1px solid #cdcdcd; + margin: 25px 0 10px 0; + } + + .i-sdc-form-textarea { + border: 1px solid @color_e; + min-height: 60px; + padding: 10px 13px; + width: 100%; + resize: none; + + &:disabled { + .disabled; + } + } + + .w-sdc-form-item { + &.error { + .i-sdc-form-input, + .i-sdc-form-select, + .i-sdc-form-textarea { + border-color: @color_h; + outline: none; + box-sizing: border-box; + } + } + } + + .i-sdc-env-form-label{ + .p_9; + overflow: hidden; + max-width: 245px; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + margin-top: 14px; + + &.required::before { + color: #f33; + content: '*'; + margin-right: 4px; + } + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts new file mode 100644 index 0000000000..80f145b9b1 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts @@ -0,0 +1,228 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + import ArtifactModel = Sdc.Models.ArtifactModel; + + interface IArtifactDeploymentStepViewModelScope extends IWizardCreationStepScope { + tableHeadersList: Array<any>; + reverse: boolean; + sortBy:string; + component: Models.Components.Component; + artifacts: Array<Models.ArtifactModel>; + editForm:ng.IFormController; + isLoading:boolean; + artifactDescriptions:any; + updateInProgress:boolean; + + addOrUpdate(artifact:Models.ArtifactModel): void; + update(artifact:Models.ArtifactModel): void; + delete(artifact:Models.ArtifactModel): void; + sort(sortBy:string): void; + noArtifactsToShow():boolean; + getValidationPattern(validationType:string, parameterType?:string):RegExp; + validateJson(json:string):boolean; + resetValue(parameter:any):void; + } + + export class ArtifactDeploymentStepViewModel implements IWizardCreationStep { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ValidationUtils', + 'ModalsHandler' + ]; + + constructor( + private $scope:IArtifactDeploymentStepViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private validationUtils: Sdc.Utils.ValidationUtils, + private ModalsHandler: Utils.ModalsHandler + ){ + this.$scope.registerChild(this); + this.$scope.setValidState(true); + this.initScope(); + } + + private initDescriptions = ():void =>{ + this.$scope.artifactDescriptions = {}; + _.forEach(this.$scope.component.deploymentArtifacts,(artifact:Models.ArtifactModel):void => { + this.$scope.artifactDescriptions[artifact.artifactLabel] = artifact.description; + }); + }; + + + private setArtifact = (artifact:Models.ArtifactModel):void =>{ + if(artifact.heatParameters) { + artifact.heatParameters.forEach((parameter:any):void => { + if (!parameter.currentValue && parameter.defaultValue) { + parameter.currentValue = parameter.defaultValue; + } else if ("" === parameter.currentValue) { + parameter.currentValue = null; + } + }); + } + if(!artifact.description || !this.$scope.getValidationPattern('string').test(artifact.description)){ + artifact.description = this.$scope.artifactDescriptions[artifact.artifactLabel]; + } + }; + + private updateAll = ():void =>{ + let artifacts:Array<Models.ArtifactModel>= []; + _.forEach(this.$scope.component.deploymentArtifacts,(artifact:Models.ArtifactModel): void => { + if(artifact.selected) { + this.setArtifact(artifact); + artifacts.push(artifact); + } + }); + this.$scope.component.updateMultipleArtifacts(artifacts); + }; + + + + private initScope = (): void => { + let self = this; + this.$scope.isLoading = false; + this.$scope.updateInProgress = false; + this.$scope.component = this.$scope.getComponent(); + this.initDescriptions(); + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + + + this.$scope.tableHeadersList = [ + {title:'Name', property: 'artifactDisplayName'}, + {title:'Type', property: 'artifactType'}, + {title:'Deployment timeout', property: 'timeout'} + ]; + + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + this.$scope.getValidationPattern = (validationType:string, parameterType?:string):RegExp => { + return this.validationUtils.getValidationPattern(validationType, parameterType); + }; + + this.$scope.validateJson = (json:string):boolean => { + if(!json){ + return true; + } + return this.validationUtils.validateJson(json); + }; + + + this.$scope.addOrUpdate = (artifact:Models.ArtifactModel): void => { + artifact.artifactGroupType = 'DEPLOYMENT'; + let artifactCopy = new Models.ArtifactModel(artifact); + this.ModalsHandler.openWizardArtifactModal(artifactCopy, this.$scope.component).then(() => { + this.$scope.artifactDescriptions[artifactCopy.artifactLabel]= artifactCopy.description; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + }) + }; + + this.$scope.noArtifactsToShow = ():boolean =>{ + return !_.some(this.$scope.artifacts, 'esId'); + }; + + this.$scope.resetValue = (parameter:any):void => { + if(!parameter.currentValue && parameter.defaultValue){ + parameter.currentValue = parameter.defaultValue; + } + else if('boolean'==parameter.type){ + parameter.currentValue = parameter.currentValue.toUpperCase(); + } + }; + + + this.$scope.$watch('editForm.$valid', ():void => { + if(this.$scope.editForm) { + this.$scope.setValidState(this.$scope.editForm.$valid); + } + }); + + this.$scope.update = (artifact:Models.ArtifactModel):void =>{ + if(false == this.$scope.isLoading) { + if(artifact.selected) { + this.$scope.isLoading = true; + this.$scope.updateInProgress = true; + let onSuccess = (responseArtifact:Models.ArtifactModel):void => { + this.$scope.artifactDescriptions[responseArtifact.artifactLabel] = responseArtifact.description; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + this.$scope.isLoading = false; + this.$scope.updateInProgress = false; + artifact.selected = !artifact.selected; + }; + + let onFailed = (error:any):void => { + console.log('Delete artifact returned error:', error); + this.$scope.isLoading = false; + this.$scope.updateInProgress = false; + artifact.selected = !artifact.selected; + }; + + this.setArtifact(artifact); + this.$scope.component.addOrUpdateArtifact(artifact).then(onSuccess, onFailed); + } else { + artifact.selected = !artifact.selected; + } + } + }; + + this.$scope.delete = (artifact:Models.ArtifactModel):void => { + let onOk = ():void => { + this.$scope.isLoading = true; + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + }; + + let onFailed = (error:any):void => { + this.$scope.isLoading = false; + console.log('Delete artifact returned error:', error); + }; + + this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed); + }; + + let title:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE"); + let message:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + }; + + public save = (callback:Function):void => { + this.updateAll(); + this.$scope.setComponent(this.$scope.component); + callback(true); + }; + + public back = (callback:Function):void => { + this.$scope.setComponent(this.$scope.component); + callback(true); + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts new file mode 100644 index 0000000000..459729c179 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts @@ -0,0 +1,304 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + import Resource = Sdc.Models.Components.Resource; + + export interface IEditArtifactStepModel { + artifactResource: Models.ArtifactModel; + artifactTypes: Array<string>; + artifactsFormList: any; + artifactFile:any; + } + + export interface IArtifactResourceFormStepViewModelScope extends ng.IScope { + editForm:ng.IFormController; + forms:any; + footerButtons: Array<any>; + isNew: boolean; + isPlaceHolderArtifact:boolean; + isLoading: boolean; + validationPattern: RegExp; + urlValidationPattern: RegExp; + labelValidationPattern: RegExp; + integerValidationPattern: RegExp; + commentValidationPattern: RegExp; + artifactType: string; + artifactGroupType:string; + editArtifactResourceModel: IEditArtifactStepModel; + defaultHeatTimeout: number; + validExtensions: any; + originalArtifactName: string; + modalInstanceArtifact:ng.ui.bootstrap.IModalServiceInstance; + selectedArtifact:string; + + fileExtensions():string; + save(): void; + close(): void; + changeArtifact(selectedArtifact:string):void; + getOptions(): Array<string>; + removeInputLabel(): void; + fileUploadRequired():string; + isDeploymentHeat():boolean; + setDefaultTimeout():void; + getFormTitle():string; + } + + export class ArtifactResourceFormStepViewModel { + + static '$inject' = [ + '$scope', + '$modalInstance', + 'artifact', + 'component', + 'Sdc.Services.CacheService', + 'ValidationPattern', + 'UrlValidationPattern', + 'LabelValidationPattern', + 'IntegerValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + 'ArtifactsUtils', + '$state', + '$modal', + '$templateCache' + ]; + + private formState:Utils.Constants.FormState; + private entityId:string; + + constructor(private $scope:IArtifactResourceFormStepViewModelScope, + private $modalInstance:ng.ui.bootstrap.IModalServiceInstance, + private artifact:Models.ArtifactModel, + private component:Models.Components.Component, + private cacheService:Services.CacheService, + private ValidationPattern:RegExp, + private UrlValidationPattern:RegExp, + private LabelValidationPattern:RegExp, + private IntegerValidationPattern:RegExp, + private CommentValidationPattern:RegExp, + private ValidationUtils:Sdc.Utils.ValidationUtils, + private ArtifactsUtils:Sdc.Utils.ArtifactsUtils, + private $state:any, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService) { + + + this.entityId = this.component.uniqueId; + this.formState = angular.isDefined(artifact.artifactLabel) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE; + + this.initScope(); + this.initEditArtifactResourceModel(); + this.initComponent(); + this.initArtifactTypes(); + } + + private initEditArtifactResourceModel = ():void => { + this.$scope.editArtifactResourceModel = { + artifactResource: null, + artifactTypes: null, + artifactsFormList: {}, + artifactFile: {} + } + }; + + private initComponent = ():void => { + this.$scope.editArtifactResourceModel.artifactResource = this.artifact; + this.$scope.originalArtifactName = this.artifact.artifactName; + let artifacts:any = Utils.Constants.ArtifactGroupType.INFORMATION === this.artifact.artifactGroupType? + this.component.artifacts : this.component.deploymentArtifacts; + this.$scope.editArtifactResourceModel.artifactsFormList = _.pick(artifacts, (artifact:Models.ArtifactModel)=> { + return artifact.artifactLabel && !artifact.esId; + }); + this.$scope.editArtifactResourceModel.artifactFile.filename= this.artifact.artifactName?this.artifact.artifactName:''; + + if(this.artifact.artifactLabel){//this is edit mode + this.$scope.editArtifactResourceModel.artifactsFormList[this.artifact.artifactLabel]= this.artifact; + this.$scope.selectedArtifact = this.artifact.artifactDisplayName; + } + }; + + private initArtifactTypes = ():void => { + let artifactTypes:any = this.cacheService.get('UIConfiguration'); + + if (Utils.Constants.ArtifactGroupType.INFORMATION === this.artifact.artifactGroupType) { + this.$scope.editArtifactResourceModel.artifactTypes = artifactTypes.artifacts.other.map((element:any)=> { + return element.name; + }); + _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> { + return _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, item) || + _.has(Utils.Constants.ArtifactType.TOSCA, item); + }) + }else if(Utils.Constants.ArtifactGroupType.DEPLOYMENT === this.artifact.artifactGroupType) { + + this.$scope.validExtensions = artifactTypes.artifacts.deployment.resourceDeploymentArtifacts; + if(this.$scope.validExtensions) { + this.$scope.editArtifactResourceModel.artifactTypes = Object.keys(this.$scope.validExtensions); + } + this.$scope.defaultHeatTimeout = artifactTypes.defaultHeatTimeout; + + if(!this.$scope.isPlaceHolderArtifact) { + _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> { + return Utils.Constants.ArtifactType.HEAT == item.substring(0,4) || + _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, item); + }) + } + + if(this.component.isResource() && (<Resource>this.component).isCsarComponent()) { + _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string) => { + return this.ArtifactsUtils.isLicenseType(item); + }) + } + } + }; + + private initScope = ():void => { + + this.$scope.modalInstanceArtifact = this.$modalInstance; + this.$scope.validationPattern = this.ValidationPattern; + this.$scope.urlValidationPattern = this.UrlValidationPattern; + this.$scope.labelValidationPattern = this.LabelValidationPattern; + this.$scope.integerValidationPattern = this.IntegerValidationPattern; + this.$scope.commentValidationPattern = this.CommentValidationPattern; + this.$scope.isLoading = false; + this.$scope.isPlaceHolderArtifact = true; + this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE); + this.$scope.artifactGroupType = this.artifact.artifactGroupType; + this.$scope.selectedArtifact = '?'; + + this.$scope.fileExtensions = ():string => { + let type:string = this.$scope.editArtifactResourceModel.artifactResource.artifactType; + return type && this.$scope.validExtensions && this.$scope.validExtensions[type].acceptedTypes ? + this.$scope.validExtensions[type].acceptedTypes.join(',') : ""; + }; + + this.$scope.removeInputLabel = ():void => { + this.$scope.isPlaceHolderArtifact = true; + }; + + this.$scope.fileUploadRequired = ():string => { + if (this.$scope.isNew===false){ + return 'false'; // This is edit mode + } else { + return 'true'; + } + }; + + this.$scope.isDeploymentHeat = ():boolean =>{ + return Utils.Constants.ArtifactGroupType.DEPLOYMENT === this.artifact.artifactGroupType + && this.$scope.editArtifactResourceModel.artifactResource + && this.$scope.editArtifactResourceModel.artifactResource.artifactType + && Utils.Constants.ArtifactType.HEAT === this.$scope.editArtifactResourceModel.artifactResource.artifactType.substring(0,4); + }; + + this.$scope.getFormTitle =(): string =>{ + let title:string = this.artifact.esId? 'Update':'Add'; + if (Utils.Constants.ArtifactGroupType.DEPLOYMENT === this.artifact.artifactGroupType) { + title += ' Deployment'; + } + title += ' Artifact'; + return title; + }; + + this.$scope.setDefaultTimeout = ():void => { + if(!this.$scope.editArtifactResourceModel.artifactResource.timeout) { + this.$scope.editArtifactResourceModel.artifactResource.timeout = this.$scope.defaultHeatTimeout; + } + }; + + this.$scope.changeArtifact = (selectedArtifact:string):void => { + let tempArtifact:Models.ArtifactModel = this.$scope.editArtifactResourceModel.artifactResource; + this.$scope.editArtifactResourceModel.artifactResource = null; + + if (selectedArtifact && selectedArtifact != '' && selectedArtifact != '?') { + let artifactResource = <Models.ArtifactModel>_.find(this.$scope.editArtifactResourceModel.artifactsFormList,{'artifactDisplayName':selectedArtifact}); + this.$scope.editArtifactResourceModel.artifactResource = new Sdc.Models.ArtifactModel(artifactResource); + this.$scope.originalArtifactName = this.$scope.editArtifactResourceModel.artifactResource.artifactName; + this.$scope.isPlaceHolderArtifact = true; + if(this.$scope.isDeploymentHeat()){ + this.$scope.setDefaultTimeout(); + } + } else if (selectedArtifact === "") { + //this.$scope.editArtifactResourceModel.artifactFile = {}; + this.$scope.editArtifactResourceModel.artifactResource = <Models.ArtifactModel>{}; + this.$scope.editArtifactResourceModel.artifactResource.artifactGroupType = this.$scope.artifactGroupType; + this.$scope.isPlaceHolderArtifact = false; + } + + if (_.size(this.$scope.editArtifactResourceModel.artifactFile) && this.$scope.editArtifactResourceModel.artifactResource) { + this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.editArtifactResourceModel.artifactFile.filename; + } + if(tempArtifact && tempArtifact.description != ''){ + this.$scope.editArtifactResourceModel.artifactResource.description = tempArtifact.description; + } + + this.initArtifactTypes(); + this.$scope.isNew = true; + + }; + + + this.$scope.save = ():void => { + this.$scope.isLoading = true; + this.$scope.editArtifactResourceModel.artifactResource.description = + this.ValidationUtils.stripAndSanitize(this.$scope.editArtifactResourceModel.artifactResource.description); + + if (this.$scope.editArtifactResourceModel.artifactFile) { + this.$scope.editArtifactResourceModel.artifactResource.payloadData = this.$scope.editArtifactResourceModel.artifactFile.base64; + this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.editArtifactResourceModel.artifactFile.filename; + } + + let onFailed = (response) => { + this.$scope.isLoading = false; + console.info('onFaild', response); + this.$scope.editArtifactResourceModel.artifactResource.esId = undefined; + }; + + let onSuccess = () => { + this.$scope.isLoading = false; + this.$scope.originalArtifactName = ""; + this.$modalInstance.close(); + + }; + + this.component.addOrUpdateArtifact(this.$scope.editArtifactResourceModel.artifactResource).then(onSuccess, onFailed); + }; + + + this.$scope.close = ():void => { + this.$modalInstance.dismiss(); + }; + + //new form layout for import asset + this.$scope.forms = {}; + this.$scope.footerButtons = [ + {'name': this.artifact.esId ? 'Update' : 'Add', 'css': 'blue', 'callback': this.$scope.save}, + {'name': 'Cancel', 'css': 'grey', 'callback': this.$scope.close} + ]; + + this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => { + this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid; + }); + + } + + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html new file mode 100644 index 0000000000..2643b99c20 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html @@ -0,0 +1,147 @@ +<sdc-modal modal="modalInstanceArtifact" type="classic" class="sdc-add-artifact" buttons="footerButtons" header="{{getFormTitle()}}" show-close-button="true"> + + <loader data-display="isLoading"></loader> + + <form novalidate class="w-sdc-form two-columns" name="forms.editForm"> + + <!--------------------- ARTIFACT FILE --------------------> + <div class="i-sdc-form-item"> + <label class="required">Upload File</label> + <file-upload id="fileUploadElement" + form-element="forms.editForm" + element-required="{{fileUploadRequired()}}" + element-name="myArtifactFile" + file-model="editArtifactResourceModel.artifactFile" + extensions="{{fileExtensions()}}" + data-ng-class="{'error': forms.editForm.myArtifactFile.$dirty && forms.editForm.myArtifactFile.$invalid}"></file-upload> + + <div class="input-error-file-upload" data-ng-show="forms.editForm.myArtifactFile.$dirty && forms.editForm.myArtifactFile.$invalid"> + <span ng-show="forms.editForm.myArtifactFile.$error.required && !forms.editForm.myArtifactFile.$error.emptyFile" translate="ADD_ARTIFACT_ERROR_FILE_REQUIRED"></span> + <span ng-show="forms.editForm.myArtifactFile.$error.maxsize" translate="VALIDATION_ERROR_MAX_FILE_SIZE"></span> + <span ng-if="'DEPLOYMENT' === artifactGroupType" ng-show="forms.editForm.myArtifactFile.$error.filetype" translate="ADD_ARTIFACT_ERROR_VALID_EXTENSIONS" translate-values="{'extensions': '{{fileExtensions()}}'}"></span> + <span ng-show="forms.editForm.myArtifactFile.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span> + </div> + </div> + <!--------------------- ARTIFACT FILE --------------------> + + <div class="w-sdc-form-columns-wrapper"> + <div class="w-sdc-form-column"> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.placeHolder.$dirty && forms.editForm.placeHolder.$invalid)}" data-ng-show="isPlaceHolderArtifact"> + <label class="i-sdc-form-label required">Artifact</label> + <select class="i-sdc-form-select" + name="placeHolder" + data-ng-disabled="!isNew" + data-ng-model="selectedArtifact" + data-ng-change="changeArtifact(selectedArtifact)" + data-tests-id="selectArtifact"> + <option disabled value="?">Select Artifact</option> + <option data-ng-repeat="(key,value) in editArtifactResourceModel.artifactsFormList">{{value.artifactDisplayName}}</option> + <option value="">Create New Artifact</option> + + </select> + + <div class="input-error" data-ng-show="forms.editForm.placeHolder.$dirty && forms.editForm.placeHolder.$invalid"> + <span ng-show="forms.editForm.placeHolder.$error.required" translate="ADD_ARTIFACT_ERROR_TYPE_REQUIRED"></span> + </div> + </div> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.artifactLabel.$dirty && forms.editForm.artifactLabel.$invalid)}" data-ng-show="!isPlaceHolderArtifact"> + <label class="i-sdc-form-label required">Artifact Label</label> + <input class="i-sdc-form-input" + data-ng-maxlength="25" + data-ng-model="editArtifactResourceModel.artifactResource.artifactLabel" + type="text" + name="artifactLabel" + data-required + data-ng-model-options="{ debounce: 200 }" + data-ng-pattern="labelValidationPattern" + maxlength="25" + data-ng-disabled="!isNew || editArtifactResourceModel.artifactResource.mandatory" + data-tests-id="artifactLabel" + autofocus/> + <span class="w-sdc-icon-cancel" data-ng-click="selectedArtifact='?'; removeInputLabel()"></span> + + <div class="input-error" data-ng-show="forms.editForm.artifactLabel.$dirty && forms.editForm.artifactLabel.$invalid"> + <span ng-show="forms.editForm.artifactLabel.$error.required" translate="ADD_ARTIFACT_ERROR_LABEL_REQUIRED"></span> + <span ng-show="forms.editForm.artifactLabel.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span> + <span ng-show="forms.editForm.artifactLabel.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.timeout.$dirty && forms.editForm.timeout.$invalid)}" + data-ng-if="isDeploymentHeat()"> + <label class="i-sdc-form-label">Deployment Timeout (minutes)</label> + <input class="i-sdc-form-input" + data-ng-maxlength="25" + data-ng-model="editArtifactResourceModel.artifactResource.timeout" + type="number" + name="timeout" + min="1" + max="2147483647" + data-ng-model-options="{ debounce: 200 }" + data-ng-pattern="integerValidationPattern" + data-ng-init="setDefaultTimeout()" + data-ng-change="setDefaultTimeout()" + maxlength="25" + data-tests-id="timeout"/> + + <div class="input-error" data-ng-show="forms.editForm.timeout.$dirty && forms.editForm.timeout.$invalid"> + <span ng-show="forms.editForm.timeout.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span> + <span ng-show="forms.editForm.timeout.$error.pattern" translate="ADD_ARTIFACT_ERROR_TIMEOUT_PATTERN"></span> + <span ng-show="forms.editForm.timeout.$error.min" translate="ADD_ARTIFACT_ERROR_TIMEOUT_MIN"></span> + </div> + </div> + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}"> + <label class="i-sdc-form-label required">Type</label> + <select class="i-sdc-form-select" + data-required + name="type" + data-ng-disabled="!isNew || editArtifactResourceModel.artifactResource.mandatory || '?'==selectedArtifact" + data-ng-model="editArtifactResourceModel.artifactResource.artifactType" + data-ng-options="type as type for type in editArtifactResourceModel.artifactTypes track by type | uppercase" + data-tests-id="artifacttype"> + <option value="">Choose Type</option> + </select> + + <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid"> + <span ng-show="forms.editForm.type.$error.required" translate="ADD_ARTIFACT_ERROR_TYPE_REQUIRED"></span> + </div> + + </div> + </div> + + <div class="w-sdc-form-column"> + + <div class="i-sdc-form-item" + data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}"> + <label class="i-sdc-form-label required">Description</label> + <textarea class="i-sdc-form-textarea" + data-ng-maxlength="256" + maxlength="256" + data-required + name="description" + data-ng-model="editArtifactResourceModel.artifactResource.description" + data-ng-model-options="{ debounce: 200 }" + data-ng-pattern="commentValidationPattern" + data-tests-id="description" + > + </textarea> + + <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid"> + <span ng-show="forms.editForm.description.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span> + <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span> + <span ng-show="forms.editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + + </div> + </div> + </div> + + <span class="w-sdc-form-note" data-ng-show="forms.editForm.$invalid && false" translate="LABEL_ALL_FIELDS_ARE_MANDATORY"></span> + + </form> + +</sdc-modal> + + diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less new file mode 100644 index 0000000000..a189c12d62 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less @@ -0,0 +1,45 @@ +.sdc-add-artifact { + + .w-sdc-form-note { + .h_9; + display: block; + position: relative; + top: 13px; + } + + .w-sdc-form { + + .i-sdc-form-textarea{ + min-height: 95px; + } + + .i-sdc-form-item.upload input[type="file"] { + display: none + } + + .w-sdc-icon-cancel { + position: absolute; + right: 7px; + top: 33px; + .sprite; + .sprite.small-x-btn-black; + .hand; + } + } + + .artifact-info { + text-align: left; + color: rgb(140, 140, 140); + font-size: 13px; + margin-top: -40px; + margin-bottom: 5px; + width: 600px; + min-height: initial; + + span { + color: #666666; + padding-left: 4px; + } + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html new file mode 100644 index 0000000000..ea4566561c --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html @@ -0,0 +1,48 @@ +<div class="artifact-step" data-ng-controller="Sdc.ViewModels.Wizard.ArtifactInformationStepViewModel"> + <div data-tests-id="add-information-artifact-button" data-tests-id="addGrey" class="w-sdc-classic-btn gray" data-ng-click="addOrUpdate({})" type="button">Add </div> + <div class="table-container-flex"> + <div class="table"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item"></div> + </div> + <div class="body"> + <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="showNoArtifactMessage()" class="no-row-text"> + There are no information artifacts to display + </div> + <div data-ng-repeat-start="artifact in artifacts| orderBy:sortBy:reverse track by $index" + class="flex-container data-row" + data-ng-class="{'selected': artifact.selected}" + data-ng-if="artifact.esId"> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected"> + <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="{{artifact.artifactDisplayName}}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="{{artifact.artifactType}}"> + {{artifact.artifactType}} + </div> + + <div class="table-btn-col flex-item" sdc-keyboard-events> + <button class="table-edit-btn" data-ng-click="addOrUpdate(artifact)"></button> + <button class="table-delete-btn" data-ng-click="delete(artifact)"> </button> + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="artifact.selected" class="item-opened" data-ng-bind="artifact.description"></div> + <button class="add-button" data-ng-repeat="artifact in artifacts track by $index" + data-ng-show="!artifact.esId" + data-tests-id="{{artifact.artifactDisplayName}}" + translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT" + translate-values="{'name': '{{artifact.artifactDisplayName}}'}" + data-ng-click="addOrUpdate(artifact)"></button> + <button class="add-button" translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER" + data-ng-click="addOrUpdate({})"></button> + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less new file mode 100644 index 0000000000..b0600ca483 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less @@ -0,0 +1,50 @@ +.artifact-step { + + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table{ + height: 412px; + margin-bottom: 0; + } + + .table-container-flex { + margin-top: 0px; + + .item-opened{ + word-wrap: break-word; + } + + + .flex-item:nth-child(1) { + padding: 5px 4px; + flex-grow: 15; + .hand; + span.table-arrow { + margin-right: 7px; + } + } + + .flex-item:nth-child(2) { + padding: 5px 4px; + flex-grow: 6; + } + + .flex-item:nth-child(3) { + padding: 5px 4px; + flex-grow: 3; + padding-top: 10px; + } + + .flex-item:nth-child(4) { + padding: 5px 4px; + flex-grow: 1; + } + + } + +} + + diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts new file mode 100644 index 0000000000..f6e25a53a3 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts @@ -0,0 +1,168 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + import ArtifactModel = Sdc.Models.ArtifactModel; + + export interface IArtifactInformationStepScope extends IWizardCreationScope { + artifacts: Array<Models.ArtifactModel>; + tableHeadersList: Array<any>; + artifactType: string; + isResourceInstance:boolean; + downloadFile:Models.IFileDownload; + isLoading:boolean; + sortBy:string; + reverse:boolean; + component:Models.Components.Component; + + getTitle(): string; + addOrUpdate(artifact:Models.ArtifactModel): void; + delete(artifact:Models.ArtifactModel): void; + download(artifact:Models.ArtifactModel): void; + clickArtifactName(artifact:any):void; + openEditEnvParametersModal(artifactResource: Models.ArtifactModel):void; + sort(sortBy:string): void; + showNoArtifactMessage():boolean; + } + + export class ArtifactInformationStepViewModel implements IWizardCreationStep { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'Sdc.Services.SharingService', + '$state', + 'sdcConfig', + 'ModalsHandler' + ]; + + constructor(private $scope:IArtifactInformationStepScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private sharingService:Sdc.Services.SharingService, + private $state:any, + private sdcConfig:Models.IAppConfigurtaion, + private ModalsHandler: Utils.ModalsHandler) { + this.$scope.registerChild(this); + this.$scope.setValidState(true); + this.initScope(); + } + + + private getMappedObjects():any { + return { + normal: this.$scope.component.artifacts + }; + } + + private initScope = ():void => { + let self = this; + this.$scope.isLoading = false; + this.$scope.sortBy = 'artifactDisplayName'; + this.$scope.reverse = false; + + this.$scope.artifactType = 'normal'; + this.$scope.getTitle = ():string => { + return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts'; + + }; + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'} + ]; + + + this.$scope.component = this.$scope.getComponent(); + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts); + + + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + + this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => { + artifact.artifactGroupType = 'INFORMATIONAL'; + this.ModalsHandler.openWizardArtifactModal(artifact, this.$scope.getComponent()).then(() => { + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts); + }); + }; + + this.$scope.showNoArtifactMessage = ():boolean => { + let artifacts:any = []; + artifacts = _.filter(this.$scope.artifacts, (artifact:Models.ArtifactModel)=> { + return artifact.esId; + }); + + if (artifacts.length === 0) { + return true; + } + return false; + } + + this.$scope.delete = (artifact:Models.ArtifactModel):void => { + + let onOk = ():void => { + this.$scope.isLoading = true; + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts); + }; + + let onFailed = (error:any):void => { + console.log('Delete artifact returned error:', error); + this.$scope.isLoading = false; + }; + + this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed); + }; + + let title:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE"); + let message:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + + this.$scope.clickArtifactName = (artifact:any) => { + if ('deployment' !== this.$scope.artifactType || 'HEAT' !== artifact.artifactType || !artifact.esId) { + this.$scope.addOrUpdate(artifact); + } + + }; + + } + + public save = (callback:Function):void => { + this.$scope.setComponent(this.$scope.component); + callback(true); + } + + public back = (callback:Function):void => { + callback(true); + } + + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html new file mode 100644 index 0000000000..db975caf47 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html @@ -0,0 +1,270 @@ +<div include-padding="true" class="sdc-wizard-general-step"> + <div ng-controller="Sdc.ViewModels.Wizard.GeneralStepViewModel"> + <form novalidate class="w-sdc-form" name="editForm"> + <div class="w-sdc-form-section-container"> + + <!--------------------- IMPORT TOSCA FILE --------------------> + <file-upload id="fileUploadElement" + ng-if="!isCreate" + element-name="fileElement" + element-disabled="{{!isNew}}" + form-element="editForm" + file-model="model.tosca" + extensions="{{toscaFileExtensions}}" + data-ng-class="{'error': !editForm.fileElement.$valid || !model.tosca.filename}"></file-upload> + + <div class="input-error-file-upload" data-ng-show="!isCreate && (!editForm.fileElement.$valid || !model.tosca.filename)"> + <!-- editForm.fileElement.$error.required <== Can not use this, because the browse is done from outside for the first time --> + <span ng-show="!model.tosca.filename && !editForm.fileElement.$error.emptyFile" translate="NEW_SERVICE_RESOURCE_ERROR_TOSCA_FILE_REQUIRED"></span><!-- Required --> + <span ng-show="editForm.fileElement.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span> + <span ng-show="editForm.fileElement.$error.filetype" translate="NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS" translate-values="{'extensions': '{{toscaFileExtensions}}' }"></span> + </div> + <!--------------------- IMPORT TOSCA FILE --------------------> + + <div class="w-sdc-form-columns-wrapper"> + + <div class="w-sdc-form-column"> + + <!--------------------- NAME --------------------> + <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.componentName)}"> + <label class="i-sdc-form-label required">Name</label> + <input class="i-sdc-form-input" + name="componentName" + data-ng-init="isNew && validateName(true)" + data-ng-change="validateName()" + data-ng-maxlength="{{component.isProduct()?'25':'50'}}" + maxlength="{{component.isProduct()?'25':'50'}}" + data-ng-minlength="{{component.isProduct()?'4':'0'}}" + minlength="{{component.isProduct()?'4':'0'}}" + data-ng-model="model.name" + type="text" + data-required + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.validationPattern" + data-ng-disabled="model.isAlreadyCertified" + data-tests-id="name" + autofocus + /> + + <div class="input-error" data-ng-show="validateField(editForm.componentName)"> + <span ng-show="editForm.componentName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span> + <span ng-show="editForm.componentName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span> + <span ng-show="editForm.componentName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span> + <span ng-show="editForm.componentName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span> + <span ng-show="editForm.componentName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- NAME --------------------> + + <!--------------------- FULL NAME --------------------> + <div ng-if="component.isProduct()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.fullName)}"> + <label class="i-sdc-form-label required">Full Name</label> + <input class="i-sdc-form-input" + name="fullName" + data-ng-change="validateName()" + data-ng-maxlength="100" + maxlength="100" + data-ng-minlength="4" + minlength="4" + data-ng-model="model.fullName" + type="text" + data-required + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.validationPattern" + data-tests-id="fullName" + autofocus + /> + + <div class="input-error" data-ng-show="validateField(editForm.fullName)"> + <span ng-show="editForm.fullName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span> + <span ng-show="editForm.fullName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span> + <span ng-show="editForm.fullName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span> + <span ng-show="editForm.fullName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span> + <span ng-show="editForm.fullName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- NAME --------------------> + + <!--------------------- DESCRIPTION --------------------> + <div class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.description)}"> + <label class="i-sdc-form-label required">Description</label> + <textarea class="i-sdc-form-textarea description" + name="description" + data-ng-maxlength="1024" + data-required + data-ng-model="model.description" + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.commentValidationPattern" + maxlength="1024" + data-tests-id="description"></textarea> + <!-- placeholder="Description here..." --> + + <div class="input-error" data-ng-show="validateField(editForm.description)"> + <span ng-show="editForm.description.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_DESCRIPTION_REQUIRED"></span> + <span ng-show="editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '1024' }"></span> + <span ng-show="editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- DESCRIPTION --------------------> + + <!--------------------- CATEGORIES --------------------> + <div class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.category)}" + data-ng-if="categories && categories.length && !component.isProduct()"> + <label class="i-sdc-form-label required">Category</label> + <select class="i-sdc-form-select" + data-required + name="category" + data-ng-change="setIconToDefault()" + data-ng-disabled="model.isAlreadyCertified" + data-ng-model="model.category" + data-tests-id="selectGeneralCategory" + > + <option value="">Select category</option> + <optgroup ng-if="component.isResource()" data-ng-repeat="mainCategory in categories | orderBy:['name']" label="{{mainCategory.name}}" data-tests-id="{{mainCategory.name}}"> + <option data-ng-repeat="subCategory in mainCategory.subcategories track by $index" + data-ng-selected="model.category===calculateUnique(mainCategory.name,subCategory.name)" + data-tests-id="{{subCategory.name}}" + value="{{calculateUnique(mainCategory.name,subCategory.name)}}">{{subCategory.name}} + + </option> + </optgroup> + <option ng-if="component.isService()" data-ng-repeat="mainCategory in categories | orderBy:['name']" + data-ng-selected="model.category===mainCategory.name" + value="{{mainCategory.name}}" + data-tests-id="{{mainCategory.name}}">{{mainCategory.name}}</option> + </select> + + <div class="input-error" data-ng-show="validateField(editForm.category)"> + <span ng-show="editForm.category.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CATEGORY_REQUIRED"></span> + </div> + </div> + <!--------------------- CATEGORIES --------------------> + + <!--------------------- VENDOR NAME --------------------> + <div ng-if="component.isResource()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.vendorName)}"> + <label class="i-sdc-form-label required">Vendor</label> + <input class="i-sdc-form-input" type="text" + data-ng-model="model.vendorName" + data-ng-model-options="{ debounce: 500 }" + data-ng-maxlength="25" + data-required + ng-click="oldValue = model.vendorName" + name="vendorName" + data-ng-change="onVendorNameChange(oldValue)" + data-ng-pattern="validation.vendorValidationPattern" + maxlength="25" + data-ng-disabled="model.isAlreadyCertified" + data-tests-id="vendorName" + /> + + <div class="input-error" data-ng-show="validateField(editForm.vendorName)"> + <span ng-show="editForm.vendorName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_NAME_REQUIRED"></span> + <span ng-show="editForm.vendorName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span> + <span ng-show="editForm.vendorName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + + </div> + + <!--------------------- VENDOR NAME --------------------> + + <!--------------------- VENDOR RELEASE --------------------> + <div ng-if="component.isResource()" + class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.vendorRelease)}"> + <label class="i-sdc-form-label required">Vendor Release</label> + <input class="i-sdc-form-input" type="text" + data-ng-model="model.vendorRelease" + data-ng-model-options="{ debounce: 500 }" + data-ng-maxlength="25" + data-required + name="vendorRelease" + data-ng-pattern="validation.vendorValidationPattern" + maxlength="25" + data-tests-id="vendorRelease" + /> + + <div class="input-error" data-ng-show="validateField(editForm.vendorRelease)"> + <span ng-show="editForm.vendorRelease.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_RELEASE_REQUIRED"></span> + <span ng-show="editForm.vendorRelease.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span> + <span ng-show="editForm.vendorRelease.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- VENDOR RELEASE --------------------> + + + + </div><!-- Close w-sdc-form-column --> + + <div class="w-sdc-form-column"> + + <!--------------------- RESOURCE TAGS --------------------> + <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.tags)}"> + <label class="i-sdc-form-label">Tags</label> + + <sdc-tags form-element="editForm" element-name="tags" max-tags="20" class="i-sdc-form-item-tags" tags="model.tags" pattern="validation.tagValidationPattern" special-tag="model.name"></sdc-tags> + + <div class="input-error" data-ng-show="validateField(editForm.tags)"> + <span ng-show="editForm.tags.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- RESOURCE TAGS --------------------> + + <!--------------------- CONTACT ID --------------------> + <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.contactId)}"> + <label class="i-sdc-form-label required" translate="GENERAL_LABEL_CONTACT_ID"></label> + <input class="i-sdc-form-input disabled" type="text" + data-ng-model="model.userId" + data-required + name="contactId" + data-ng-pattern="validation.contactIdValidationPattern" + data-ng-model-options="{ debounce: 500 }" + data-tests-id="userId" + maxlength="50" + /> + + <div class="input-error" data-ng-show="validateField(editForm.contactId)"> + <span ng-show="editForm.contactId.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_REQUIRED"></span> + <span ng-show="editForm.contactId.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_NOT_VALID"></span> + </div> + </div> + <!--------------------- CONTACT ID --------------------> + + <!--------------------- PROJECT CODE --------------------> + <div class="i-sdc-form-item" data-ng-if="!component.isResource()" + data-ng-class="{'error': validateField(editForm.projectCode)}"> + <label class="i-sdc-form-label required" translate="GENERAL_LABEL_PROJECT_CODE"></label> + <input class="i-sdc-form-input" type="text" + data-ng-model="model.projectCode" + data-ng-model-options="{ debounce: 500 }" + data-ng-maxlength="128" + data-required + name="projectCode" + data-ng-pattern="validation.projectCodeValidationPattern" + maxlength="50" + data-tests-id="projectCode" + /> + + <div class="input-error" data-ng-show="validateField(editForm.projectCode)"> + <span ng-show="editForm.projectCode.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_REQUIRED"></span> + <span ng-show="editForm.projectCode.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_NOT_VALID"></span> + </div> + </div> + <!--------------------- VENDOR RELEASE --------------------> + + + </div><!-- Close w-sdc-form-column --> + + </div><!-- Close w-sdc-form-column --> + + <div class="w-sdc-form-messages-wrapper"> + <span class="w-sdc-form-messages-msg" data-ng-show="isSaved"><span class="w-sdc-form-messages-msg-v"></span>Your resource has been saved</span> + </div> + + </div><!-- Close w-sdc-form-section-container --> + + </form> + + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less new file mode 100644 index 0000000000..700997a423 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less @@ -0,0 +1,34 @@ +.sdc-wizard-general-step { + + .w-sdc-form { + padding: 0; + + .w-sdc-form-section-container { + text-align: center; + } + + .i-sdc-form-item { + &.upload { + margin-top: 0; + width: auto; + padding: 10px; + } + } + + .template-desc { + border: 1px dashed @border_color_f; + height: 130px; + overflow: hidden; + padding: 10px 6px 6px 6px; + margin-top: 10px; + } + + .sdc-tag .tag { + max-width: 225px; + } + + } + +} + + diff --git a/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts new file mode 100644 index 0000000000..74c681e433 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts @@ -0,0 +1,381 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + import ISubCategory = Sdc.Models.ISubCategory; + import IMainCategory = Sdc.Models.IMainCategory; + 'use strict'; + + /* + * TODO: The template (derived from is not necessary here). + * Need to delete it from all remarks. + * */ + + export class UIModel { + tosca:Sdc.Directives.FileUploadModel; + name:string; + description:string; + vendorName:string; + vendorRelease:string; + category:string; + tags:Array<string>; + userId:string; + icon:string; + projectCode:string; + fullName:string; + isAlreadyCertified:boolean; + } + + export class Validation { + validationPattern: RegExp; + contactIdValidationPattern: RegExp; + tagValidationPattern: RegExp; + vendorValidationPattern: RegExp; + commentValidationPattern: RegExp; + projectCodeValidationPattern: RegExp; + } + + export interface IGeneralStepScope extends IWizardCreationStepScope { + model:UIModel; + validation:Validation; + editForm:ng.IFormController; + component: Models.Components.Component; + categories: Array<IMainCategory>; + latestComponentName:string; + latestCategoryId: string; + latestVendorName: string; + isNew:boolean; + toscaFileExtensions:any; + isCreate:boolean; + + onToscaFileChange():void + validateField(field:any):boolean; + validateName(isInit:boolean): void; + calculateUnique(mainCategory:string, subCategory:string):string; // Build unique string from main and sub category + calculatedTagsMaxLength():number; + setIconToDefault():void; + onVendorNameChange(oldVendorName: string): void; + } + + export class GeneralStepViewModel implements IWizardCreationStep { + + static '$inject' = [ + '$scope', + 'Sdc.Services.CacheService', + 'ValidationPattern', + 'ContactIdValidationPattern', + 'TagValidationPattern', + 'VendorValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + 'sdcConfig', + 'ComponentFactory', + 'ProjectCodeValidationPattern' + ]; + + constructor(private $scope:IGeneralStepScope, + private cacheService:Services.CacheService, + private ValidationPattern:RegExp, + private ContactIdValidationPattern:RegExp, + private TagValidationPattern:RegExp, + private VendorValidationPattern:RegExp, + private CommentValidationPattern: RegExp, + private ValidationUtils: Sdc.Utils.ValidationUtils, + private sdcConfig:Models.IAppConfigurtaion, + private ComponentFactory: Sdc.Utils.ComponentFactory, + private ProjectCodeValidationPattern:RegExp + ) { + + this.$scope.registerChild(this); + this.initScopeValidation(); + this.initScopeMethods(); + this.initScope(); + this.$scope.isCreate = this.$scope.data.importFile === undefined; + } + + private initScopeValidation = (): void => { + this.$scope.validation = new Validation(); + this.$scope.validation.validationPattern = this.ValidationPattern; + this.$scope.validation.contactIdValidationPattern = this.ContactIdValidationPattern; + this.$scope.validation.tagValidationPattern = this.TagValidationPattern; + this.$scope.validation.vendorValidationPattern = this.VendorValidationPattern; + this.$scope.validation.commentValidationPattern = this.CommentValidationPattern; + this.$scope.validation.projectCodeValidationPattern = this.ProjectCodeValidationPattern; + }; + + private initScope = ():void => { + + // Init UIModel + this.$scope.model = new UIModel(); + + // Init categories + if(this.$scope.data.componentType === Utils.Constants.ComponentType.RESOURCE){ + this.$scope.categories = this.cacheService.get('resourceCategories'); + } + if (this.$scope.data.componentType === Utils.Constants.ComponentType.SERVICE) { + this.$scope.categories = this.cacheService.get('serviceCategories'); + } + + this.$scope.model.category=''; + + //init file extenstions + this.$scope.toscaFileExtensions = this.sdcConfig.toscaFileExtension; + + // Init Tosca import file + if (this.$scope.data.importFile) { + this.$scope.model.tosca = this.$scope.data.importFile; + } + + // Case insert or update + this.$scope.component = this.$scope.getComponent(); + if ( this.$scope.component!==undefined){ + // Update mode + + //this.$scope.latestCategoryId = this.$scope.component[0].uniqueId; + //this.$scope.latestVendorName = this.$scope.component.vendorName; + this.$scope.latestComponentName = this.$scope.component.name; + this.$scope.isNew=false; + this.resource2ModelUi(this.$scope.component); + } else { + // Create mode + this.$scope.isNew=true; + this.$scope.model.tags=[]; // Init tags + this.$scope.model.userId = this.cacheService.get("user").userId; // Fill user ID from logged in user + this.$scope.model.icon = Utils.Constants.DEFAULT_ICON; // Set the default icon + this.$scope.component = this.ComponentFactory.createEmptyComponent(this.$scope.data.componentType); + } + }; + + private initScopeMethods = ():void => { + + this.$scope.validateField = (field:any):boolean => { + if (field && field.$dirty && field.$invalid){ + return true; + } + return false; + }; + + this.$scope.validateName = (isInit:boolean):void => { + if (isInit===undefined){isInit=false;} + + let name = this.$scope.model.name; + if (!name || name===""){ + if (this.$scope.editForm + && this.$scope.editForm["componentName"] + && this.$scope.editForm["componentName"].$error){ + + // Clear the error name already exists + this.$scope.editForm["componentName"].$setValidity('nameExist', true); + } + + return; + } + let subtype:string = Utils.Constants.ComponentType.RESOURCE == this.$scope.data.componentType? + this.$scope.data.importFile? 'VFC':'VF' : undefined; + + let onFailed = (response) => { + //console.info('onFaild', response); + //this.$scope.isLoading = false; + }; + + let onSuccess = (validation:Models.IValidate) => { + this.$scope.editForm["componentName"].$setValidity('nameExist', validation.isValid); + }; + + if (isInit){ + // When page is init after update + if (this.$scope.model.name !== this.$scope.latestComponentName){ + if(!this.$scope.component.isProduct()) {//TODO remove when backend is ready + this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed); + } + } + } else { + // Validating on change (has debounce) + if (this.$scope.editForm + && this.$scope.editForm["componentName"] + && this.$scope.editForm["componentName"].$error + && !this.$scope.editForm["componentName"].$error.pattern + && this.$scope.model.name !== this.$scope.latestComponentName + ) { + if(!this.$scope.component.isProduct()) { //TODO remove when backend is ready + this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed); + } + } else if (this.$scope.model.name === this.$scope.latestComponentName) { + // Clear the error + this.$scope.editForm["componentName"].$setValidity('nameExist', true); + } + } + }; + + this.$scope.calculateUnique = (mainCategory:string, subCategory:string):string => { + let uniqueId: string = mainCategory; + if(subCategory) { + uniqueId += "_#_" + subCategory; // Set the select category combobox to show the selected category. + } + return uniqueId; + }; + + // Notify the parent if this step valid or not. + this.$scope.$watch("editForm.$valid", (newVal, oldVal) => { + //console.log("editForm validation: " + newVal); + this.$scope.setValidState(newVal); + }); + + this.$scope.setIconToDefault = ():void => { + this.$scope.model.icon = Utils.Constants.DEFAULT_ICON; + }; + + this.$scope.onVendorNameChange = (oldVendorName: string):void => { + if(this.$scope.component.icon === oldVendorName) { + this.$scope.setIconToDefault(); + } + }; + }; + + public save = (callback:Function):void => { + this.modelUi2Resource(); + + let onFailed = (response) => { + callback(false); + }; + + let onSuccess = (component:Models.Components.Component) => { + this.$scope.component = component; + this.$scope.setComponent(this.$scope.component); + this.$scope.latestComponentName = (component.name); + callback(true); + }; + + try { + //Send the form with attached tosca file. + if (this.$scope.isNew===true) { + this.ComponentFactory.createComponentOnServer(this.$scope.component).then(onSuccess, onFailed); + } else { + this.$scope.component.updateComponent().then(onSuccess, onFailed); + } + }catch(e){ + //console.log("ERROR: Error in updating/creating component: " + e); + callback(false); + } + + }; + + public back = (callback:Function):void => { + callback(true); + } + + // Fill the resource properties object with data from UIModel + private modelUi2Resource = ():void => { + + this.$scope.component.name = this.$scope.model.name; + this.$scope.component.description = this.ValidationUtils.stripAndSanitize(this.$scope.model.description); + this.$scope.component.vendorName = this.$scope.model.vendorName; + this.$scope.component.vendorRelease = this.$scope.model.vendorRelease; + this.$scope.component.tags = angular.copy(this.$scope.model.tags); + this.$scope.component.tags.push(this.$scope.model.name); + this.$scope.component.contactId = this.$scope.model.userId; + this.$scope.component.icon = this.$scope.model.icon; + + if(this.$scope.component.isResource()) { + (<Models.Components.Resource>this.$scope.component).resourceType = "VF"; + + // Handle the tosca file + if (this.$scope.model.tosca && this.$scope.isNew) { + (<Models.Components.Resource>this.$scope.component).payloadData = this.$scope.model.tosca.base64; + (<Models.Components.Resource>this.$scope.component).payloadName = this.$scope.model.tosca.filename; + } + + this.$scope.component.categories = this.convertCategoryStringToOneArray(); + } + + if(this.$scope.component.isProduct()) { + this.$scope.component.projectCode = this.$scope.model.projectCode; + // Handle the tosca file + this.$scope.component.categories = undefined; + (<Models.Components.Product>this.$scope.component).contacts = new Array<string>(); + (<Models.Components.Product>this.$scope.component).contacts.push(this.$scope.component.contactId); + (<Models.Components.Product>this.$scope.component).fullName = this.$scope.model.fullName; + } + + if(this.$scope.component.isService()) { + this.$scope.component.projectCode = this.$scope.model.projectCode; + this.$scope.component.categories = this.convertCategoryStringToOneArray(); + } + }; + + // Fill the UIModel from data from resource properties + private resource2ModelUi = (component: Models.Components.Component):void => { + this.$scope.model.name = component.name; + this.$scope.model.description = component.description; + this.$scope.model.vendorName = component.vendorName; + this.$scope.model.vendorRelease = component.vendorRelease; + this.$scope.model.tags = _.reject(component.tags, (item)=>{return item===component.name}); + this.$scope.model.userId = component.contactId; + this.$scope.model.icon = component.icon; + this.$scope.model.projectCode = component.projectCode; + this.$scope.model.isAlreadyCertified = component.isAlreadyCertified(); + + if(!this.$scope.component.isProduct()) { + this.$scope.model.category = this.convertCategoryOneArrayToString(component.categories); + } + + if(component.isProduct()) { + this.$scope.model.fullName = (<Models.Components.Product>component).fullName; + + } + + }; + + // Convert category string MainCategory_#_SubCategory to Array with one item (like the server except) + private convertCategoryStringToOneArray = ():Array<Models.IMainCategory> => { + let tmp = this.$scope.model.category.split("_#_"); + let mainCategory = tmp[0]; + let subCategory = tmp[1]; + + // Find the selected category and add the relevant sub category. + let selectedMainCategory:IMainCategory = <Models.IMainCategory>_.find(this.$scope.categories, function (item) { + return item["name"] === mainCategory + }); + let mainCategoryClone = jQuery.extend(true, {}, selectedMainCategory); + if(subCategory) { + mainCategoryClone['subcategories'] = [{ + "name": subCategory + }]; + } + let tmpSelected = <Models.IMainCategory> mainCategoryClone; + + let result:Array<Models.IMainCategory> = []; + result.push(tmpSelected); + + return result; + }; + + private convertCategoryOneArrayToString = (categories:Array<Models.IMainCategory>):string => { + let mainCategory:string = categories[0].name; + let subCategory:string = ''; + if(categories[0].subcategories) { + subCategory = categories[0].subcategories[0].name; + } + return this.$scope.calculateUnique(mainCategory, subCategory); + }; + + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html new file mode 100644 index 0000000000..7fc3e9224f --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html @@ -0,0 +1,40 @@ +<div class="hierarchy-step" data-ng-controller="Sdc.ViewModels.Wizard.HierarchyStepViewModel"> + <div class="dropdown-container" clicked-outside="{onClickedOutside: 'clickOutside()', clickedOutsideEnable: 'true'}" > + <input placeholder="Add Group" data-ng-click="onInputTextClicked()" class="dropdown-input-text" data-ng-model="search.filterTerms" data-ng-model-options="{debounce: 200}"/> + <div data-ng-class="{'show': showDropDown}" class="dropdown-content" > + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="false" class="scrollbar-container"> + <div ng-repeat="category in categoriesOptions track by $index"> + <div ng-repeat="subcategory in category.subcategories track by $index"> + <div class="dropdown-option" ng-show="!category.filteredGroup || category.filteredGroup.length > 0"> + <div class="category-container"> + <div class="category">{{category.name}}</div> + <div class="subcategory">{{subcategory.name}}</div> + </div> + <div class="groupings-container"> + <div ng-init="group.filterTerms = group.name + ' ' + category.name + ' ' + subcategory.name" + ng-repeat="group in (category.filteredGroup = (subcategory.groupings | filter:search )) track by $index"> + <div class="group" data-ng-disabled="group.isDisabled" data-ng-class="{'disabled-group': group.isDisabled}" ng-click="onGroupSelected(category, subcategory, group)"> + <span >{{group.name}}</span> + </div> + </div> + </div> + </div> + </div> + </div> + </perfect-scrollbar> + </div> + </div> + <div class="hierarchy-groups-container"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div ng-if="!product.categories.length || product.categories.length === 0" class="no-groups-text" translate="NEW_PRODUCT_NO_CATEGORIES_TO_DISPLAY"></div> + <div ng-repeat="category in product.categories track by $index"> + <div ng-repeat="subcategory in category.subcategories track by $index"> + <div class="group-tag" ng-repeat="group in subcategory.groupings track by $index" + data-ng-init="tooltip = '<b>' + category.name + '</b><br />' + subcategory.name"> + <sdc-tag data-on-delete="deleteGroup(uniqueId)" data-tag-data="{tag: group.name, tooltip: tooltip, id: group.uniqueId }"></sdc-tag> + </div> + </div> + </div> + </perfect-scrollbar> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less new file mode 100644 index 0000000000..74786c127a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less @@ -0,0 +1,125 @@ +.hierarchy-step { + margin-top: 35px; + + .scrollbar-container{ + max-height:400px; + .perfect-scrollbar; + } + + .dropdown-container { + position: relative; + display: inline-block; + width: 100%; + + &:after{ + top: 47%; + right: 1%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(0, 0, 0, 0); + border-top-color: black; + border-width: 4px; + margin-left: -4px; + } + + .dropdown-input-text { + width: 100%; + padding: 4px 10px; + } + + .dropdown-content { + .perfect-scrollbar; + border: 1px solid #d8d8d8; + display: none; + position: absolute; + overflow: hidden; + width: 100%; + .bg_c; + max-height: 400px; + z-index: 999999; + + .dropdown-option { + border-bottom: 1px solid #d8d8d8; + display: inline-block; + width: 100%; + } + + .category-container{ + width: 250px; + float: left; + padding-left: 5px; + + .category { + .bold; + padding: 3px 3px 2px 3px; + &:after{ + .sprite; + .arrow-left; + content: ''; + margin-left: 5px; + transform: rotate(180deg); + } + } + .subcategory { + padding-left: 3px; + } + } + + .groupings-container{ + display: inline-block; + width: 424px; + border-left: 1px solid #d8d8d8; + min-height: 55px; + .group{ + padding: 3px 3px 3px 10px; + &:hover{ + .hand; + .bg_n; + } + &.disabled-group { + opacity: 0.5; + &:hover{ + cursor: auto; + .bg_c; + } + } + } + } + + .seperator { + height: 1px; + width: 100%; + .bg_j; + margin: 5px 0px; + } + } + .show { + display: block; + } + } + + .hierarchy-groups-container{ + .b_9; + width: 100%; + border: 1px solid #d8d8d8; + height: 425px; + padding: 15px; + text-align: center; + + .no-group-text{ + text-align: center; + margin-top:25px; + a { + cursor: pointer; + } + } + .group-tag{ + display: inline-block; + float: left; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts new file mode 100644 index 0000000000..a974c0af81 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts @@ -0,0 +1,149 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + export interface IHierarchyStepScope extends IWizardCreationScope { + + categoriesOptions: Array<Models.IMainCategory>; + product:Models.Components.Product; + isLoading:boolean; + showDropDown:boolean; + + onInputTextClicked():void; + onGroupSelected(category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void; + clickOutside():void; + deleteGroup(uniqueId:string):void; + } + + export class HierarchyStepViewModel implements IWizardCreationStep { + + static '$inject' = [ + '$scope', + 'Sdc.Services.CacheService', + 'ComponentFactory' + ]; + + constructor(private $scope:IHierarchyStepScope, + private cacheService:Sdc.Services.CacheService, + private ComponentFactory: Sdc.Utils.ComponentFactory) { + + this.$scope.registerChild(this); + this.$scope.setValidState(true); + this.$scope.product = <Models.Components.Product>this.$scope.getComponent(); + this.initScope(); + } + + private initCategories = () => { + this.$scope.categoriesOptions = angular.copy(this.cacheService.get('productCategories')); + let selectedGroup:Array<Models.IGroup> = []; + _.forEach(this.$scope.product.categories, (category: Models.IMainCategory) => { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => { + selectedGroup = selectedGroup.concat(subcategory.groupings); + }); + }); + _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => { + _.forEach(subcategory.groupings, (group:Models.ISubCategory) => { + let componentGroup:Models.IGroup = _.find(selectedGroup, (componentGroupObj) => { + return componentGroupObj.uniqueId == group.uniqueId; + }); + if(componentGroup){ + group.isDisabled = true; + } + }); + }); + }); + }; + + private setFormValidation = ():void => { + if(!this.$scope.product.categories || this.$scope.product.categories.length === 0){ + this.$scope.setValidState(false); + } + else{ + this.$scope.setValidState(true); + } + + }; + + private initScope = ():void => { + this.$scope.isLoading= false; + this.$scope.showDropDown =false; + this.initCategories(); + this.setFormValidation(); + + this.$scope.onGroupSelected = (category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void => { + this.$scope.showDropDown = false; + this.$scope.product.addGroup(category, subcategory, group); + group.isDisabled = true; + this.setFormValidation(); + }; + + this.$scope.onInputTextClicked = ():void => {//just edit the component in place, no pop up nor server update ? + this.$scope.showDropDown = !this.$scope.showDropDown; + }; + + this.$scope.clickOutside = (): any => { + this.$scope.showDropDown = false; + }; + + this.$scope.deleteGroup = (uniqueId:string) : void => { + //delete group from component + this.$scope.product.deleteGroup(uniqueId); + this.setFormValidation(); + //enabled group + _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => { + let groupObj:Models.IGroup = _.find (subcategory.groupings, (group) => { + return group.uniqueId === uniqueId; + }); + if(groupObj){ + groupObj.isDisabled = false; + } + }); + }); + } + }; + + public save = (callback:Function):void => { + let onFailed = (response) => { + callback(false); + }; + + let onSuccess = (component: Models.Components.Component) => { + this.$scope.product = <Models.Components.Product> this.ComponentFactory.createComponent(component); + this.$scope.setComponent(this.$scope.product); + callback(true); + }; + + try { + this.$scope.product.updateComponent().then(onSuccess, onFailed); + }catch(e){ + //console.log("ERROR: Error in updating/creating component: " + e); + callback(false); + } + }; + + public back = (callback:Function):void => { + this.save(callback); + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html new file mode 100644 index 0000000000..2ae386283c --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html @@ -0,0 +1,26 @@ +<div class="icons-step" data-ng-controller="Sdc.ViewModels.Wizard.IconsStepViewModel"> + + <form novalidate class="w-sdc-form" name="iconForm"> + <label class="i-sdc-form-label icons-label required">Icons</label> + <div class="selected-icon-container"> + <div class="i-sdc-form-item-suggested-icon medium selected-icon {{iconSprite}} {{component.icon}}" + ng-model="component.icon" + tooltips tooltip-content='{{component.icon | translate}}' + > + </div> + </div> + + <label class="i-sdc-form-label icons-label required">Select one of the icons below for the asset</label> + <div class="i-sdc-form-item suggested-icons-container"> + <div class ="suggested-icon-wrapper" ng-class="component.icon==='{{iconSrc}}' ? 'selected' : '' " data-ng-repeat="iconSrc in icons track by $index"> + <div class="i-sdc-form-item-suggested-icon medium {{iconSprite}} {{iconSrc}}" data-ng-class="component.isAlreadyCertified()? 'disable':'hand'" + ng-model="component.icon" + data-tests-id="{{iconSrc}} iconBox" + data-ng-click="!component.isAlreadyCertified() && setComponentIcon(iconSrc)" + tooltips tooltip-content='{{iconSrc | translate}}' + > + </div> + </div> + </div> + </form> +</div> diff --git a/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less new file mode 100644 index 0000000000..c03c949962 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less @@ -0,0 +1,55 @@ +.icons-step { + + .w-sdc-form { + padding-top: 0px; + padding-bottom: 0px; + .selected-icon-container { + text-align: left; + border: 1px solid #cfcfcf; + clear: both; + margin-bottom: 15px; + padding-left: 3px; + padding-bottom: 3px; + .selected-icon { + margin: 8px 5px 0px 6px; + } + } + + .suggested-icons-container { + text-align: left; + border: 1px solid #cfcfcf; + clear: both; + padding-left: 3px; + height: 340px; + margin-bottom: 0px; + + .suggested-icon-wrapper { + margin: 8px 5px 0px 6px; + display: inline-block; + + &.selected { + border: 1px solid @color_p; + border-radius: 25px; + box-shadow: 0 0 2px #888; + display: inline-block; + line-height: 0px; + padding: 2px; + } + + } + .suggested-icon { + // margin: 8px 5px 0px 6px; + display: inline-block; + &.disable{ + opacity: 0.4; + } + } + + + } + + .icons-label { + float: left; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts new file mode 100644 index 0000000000..4dc5e377fa --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts @@ -0,0 +1,150 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + export interface IIconsStepScope extends IWizardCreationStepScope{ + icons : Array<string>; + component: Models.Components.Component; + iconSprite: string; + setComponentIcon(iconSrc:string): void; + } + + export class IconsStepViewModel implements IWizardCreationStep { + + static '$inject' = [ + '$scope', + 'Sdc.Services.AvailableIconsService', + 'ComponentFactory' + ]; + + constructor(private $scope:IIconsStepScope, + private availableIconsService:Services.AvailableIconsService, + private ComponentFactory: Sdc.Utils.ComponentFactory) { + + this.$scope.registerChild(this); + this.$scope.component = this.$scope.getComponent(); + this.$scope.iconSprite = this.$scope.component.iconSprite; + this.initScope(); + this.initIcons(); + + if(this.$scope.component.isResource()) { + this.initVendor(); + } + // In case there is one icons select it. + if( this.$scope.icons.length == 1 && !this.$scope.component.isAlreadyCertified()){ + this.$scope.setComponentIcon(this.$scope.icons[0]); + } + } + + private initIcons = ():void => { + + // For subcategories that where created by admin, there is no icons + this.$scope.icons = new Array<string>(); + if (this.$scope.component.categories && this.$scope.component.categories.length > 0) { + + _.forEach(this.$scope.component.categories, (category:Models.IMainCategory):void => { + if (category.icons) { + this.$scope.icons = this.$scope.icons.concat(category.icons); + } + if (category.subcategories) { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory):void => { + if (subcategory.icons) { + this.$scope.icons = this.$scope.icons.concat(subcategory.icons); + } + }); + } + }); + } + + if (this.$scope.component.isResource()) { + let resourceType:string = this.$scope.component.getComponentSubType(); + if (resourceType === 'VL') { + this.$scope.icons = ['vl']; + } + if (resourceType === 'CP') { + this.$scope.icons = ['cp']; + } + } + + if (this.$scope.icons.length === 0) { + this.$scope.icons = this.availableIconsService.getIcons(this.$scope.component.componentType); + } + + }; + + private initVendor = ():void => { + let vendors:Array<string> = this.availableIconsService.getIcons(this.$scope.component.componentType).slice(5, 19); + let vendorName = this.$scope.component.vendorName.toLowerCase(); + if ('at&t' === vendorName){ + vendorName = 'att'; + } + if ('nokia' === vendorName){ + vendorName = 'nokiasiemens'; + } + + let vendor:string = _.find(vendors, (vendor:string)=>{ + return vendor.replace(/[_]/g, '').toLowerCase() === vendorName; + }); + + if(vendor && this.$scope.icons.indexOf(vendor)===-1) { + this.$scope.icons.push(vendor); + } + }; + + private initScope():void { + this.$scope.icons = []; + + if(this.$scope.component.icon === Utils.Constants.DEFAULT_ICON){ + this.$scope.setValidState(false); + } + + this.$scope.setComponentIcon = (iconSrc:string):void => { + this.$scope.component.icon = iconSrc; + this.$scope.setValidState(true); + } + } + + save(callback:Function):void { + let onFailed = () => { + callback(false); + }; + + let onSuccess = (component:Models.Components.Component) => { + this.$scope.component = component; + this.$scope.setComponent(this.$scope.component); + callback(true); + }; + + try { + this.$scope.component.updateComponent().then(onSuccess, onFailed); + }catch(e){ + callback(false); + } + } + + public back = (callback:Function):void => { + this.save(callback); + } + + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html new file mode 100644 index 0000000000..4429451871 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html @@ -0,0 +1,57 @@ +<div class="properties-step" data-ng-controller="Sdc.ViewModels.Wizard.PropertiesStepViewModel"> + + <div class="w-sdc-classic-btn gray" data-tests-id="addGrey" data-ng-click="addOrUpdateProperty()">Add</div> + + <div class="table-container-flex"> + + <div class="table"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item"></div> + <!--div class="table-no-text-header head-row flex-item"></div--> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="component.properties.length === 0 " class="no-row-text"> + There are no properties to display <br> + click <a data-ng-click="addOrUpdateProperty()">here</a> to add one + </div> + <div data-ng-repeat-start="property in component.properties | orderBy:sortBy:reverse track by $index" + class="flex-container data-row" data-ng-class="{'selected': property.selected}" + data-ng-click="property.selected = !property.selected"> + + <div class="table-col-general flex-item text"> + <span class="sprite table-arrow" data-ng-class="{'opened': property.selected}"></span> + <span data-tests-id="{{property.name}}" tooltips tooltip-content="{{property.name}}">{{property.name}}</span> + + </div> + + <div class="table-col-general flex-item text" data-tests-id="{{property.type}}" data-ng-bind="property.type"></div> + + <div class="table-col-general flex-item text"> + <span tooltips tooltip-content="{{property.defaultValue}}" data-tests-id="{{property.defaultValue}}" data-ng-bind="property.defaultValue"></span> + </div> + + <div class="table-btn-col flex-item"> + <button class="table-edit-btn" data-ng-show="property.parentUniqueId==component.uniqueId" + data-ng-click="addOrUpdateProperty(property); $event.stopPropagation();"> </button> + <button class="table-delete-btn" data-ng-show="property.parentUniqueId==component.uniqueId" + data-ng-click="delete(property); $event.stopPropagation();"> </button> + </div> + + <!--div class="table-btn-col flex-item"> + + </div--> + </div> + <div data-ng-repeat-end="" data-ng-if="property.selected && property.description" class="item-opened" data-ng-bind="property.description"> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> + +</div> diff --git a/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less new file mode 100644 index 0000000000..0d7dad8dc2 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less @@ -0,0 +1,55 @@ +.properties-step { + + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table{ + height: 412px; + margin-bottom: 0; + } + + .table-container-flex { + margin-top: 0px; + + .text{ + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + padding: 5px 4px; + + } + + .flex-item:nth-child(1) { + flex-grow: 15; + + .hand; + span.table-arrow { + margin-right: 7px; + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 9; + } + + .flex-item:nth-child(4) { + flex-grow: 3; + //padding-top: 10px; + padding: 10px 4px; + + } + + .flex-item:nth-child(5) { + flex-grow: 1; + } + + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts new file mode 100644 index 0000000000..08dfb5e153 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts @@ -0,0 +1,123 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + interface IPropertiesStepViewModelScope extends IWizardCreationStepScope { + component: Models.Components.Component; + tableHeadersList: Array<any>; + reverse: boolean; + sortBy:string; + + addOrUpdateProperty(): void; + delete(property: Models.PropertyModel): void; + sort(sortBy:string): void; + } + + export class PropertiesStepViewModel implements IWizardCreationStep { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ModalsHandler' + ]; + + + constructor( + private $scope:IPropertiesStepViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private ModalsHandler: Utils.ModalsHandler + ){ + + this.$scope.registerChild(this); + this.$scope.setValidState(true); + this.initScope(); + } + + public save = (callback:Function):void => { + this.$scope.setComponent(this.$scope.component); + callback(true); + }; + + public back = (callback:Function):void => { + this.$scope.setComponent(this.$scope.component); + callback(true); + } + + + private openEditPropertyModal = (property: Models.PropertyModel): void => { + let viewModelsHtmlBasePath: string = '/app/scripts/view-models/'; + + let modalOptions: ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath+'wizard/property-form/property-form.html'), + controller: 'Sdc.ViewModels.Wizard.PropertyFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + property: (): Models.PropertyModel => { + return property; + }, + component: (): Models.Components.Component => { + return <Models.Components.Component> this.$scope.getComponent(); + } + } + }; + this.$modal.open(modalOptions); + }; + + private initScope = (): void => { + + let self = this; + this.$scope.component = this.$scope.getComponent(); + this.$scope.sortBy = 'name'; + this.$scope.reverse = false; + + this.$scope.tableHeadersList = [ + {title:'Name', property: 'name'}, + {title:'Type', property: 'type'}, + {title:'Default Value', property: 'defaultValue'} + ]; + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + this.$scope.addOrUpdateProperty = (property?: Models.PropertyModel): void => { + this.openEditPropertyModal(property ? property : new Models.PropertyModel()); + }; + + this.$scope.delete = (property: Models.PropertyModel): void => { + + let onOk = (): void => { + this.$scope.component.deleteProperty(property.uniqueId); + }; + let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE"); + let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts new file mode 100644 index 0000000000..3f390841ca --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts @@ -0,0 +1,163 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> + +describe("property form View Model ", () => { + + let $controllerMock:ng.IControllerService; + let $qMock:ng.IQService; + let $httpBackendMock:ng.IHttpBackendService; + let $scopeMock:Sdc.ViewModels.Wizard.IPropertyFormViewModelScope; + let $stateMock:ng.ui.IStateService; + let $stateParams:any; + let component = { + "uniqueId": "ece818e0-fd59-477a-baf6-e27461a7ce23", + "uuid": "8db823c2-6a9c-4636-8676-f5e713270dd7", + "contactId": "uf2345", + "category": "Network Layer 2-3/Router", + "creationDate": 1447235352429, + "description": "u", + "highestVersion": true, + "icon": "network", + "lastUpdateDate": 1447235370064, + "lastUpdaterUserId": "cs0008", + "lastUpdaterFullName": "Carlos Santana", + "lifecycleState": "NOT_CERTIFIED_CHECKOUT", + "name": "u", + "version": "0.1", + "type": 1, + "tags": [ + "u" + ], + "vendorName": "u", + "vendorRelease": "u", + "systemName": "U", + "$$hashKey": "object:34" + }; + + + beforeEach(angular.mock.module('sdcApp')); + + beforeEach(angular.mock.inject((_$controller_:ng.IControllerService, + _$httpBackend_:ng.IHttpBackendService, + _$rootScope_, + _$q_:ng.IQService, + _$state_:ng.ui.IStateService, + _$stateParams_:any) => { + + $controllerMock = _$controller_; + $httpBackendMock = _$httpBackend_ + $scopeMock = _$rootScope_.$new(); + $qMock = _$q_; + $stateMock = _$state_; + $stateParams = _$stateParams_; + + + //handle all http request thet not relevant to the tests + $httpBackendMock.expectGET(/.*languages\/en_US.json.*/).respond(200, JSON.stringify({})); + // $httpBackendMock.expectGET(/.*resources\/certified\/abstract.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET(/.*rest\/version.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET(/.*configuration\/ui.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET(/.*user\/authorize.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET(/.*categories\/services.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET(/.*categories\/resources.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET(/.*categories\/products.*/).respond(200, JSON.stringify({})); + $httpBackendMock.expectGET('http://feHost:8181/sdc1/feProxy/rest/version').respond(200, JSON.stringify({})); + + /** + * Mock the service + * @type {any} + */ + //getAllEntitiesDefered = $qMock.defer(); + //getAllEntitiesDefered.resolve(getAllEntitiesResponseMock); + //entityServiceMock = jasmine.createSpyObj('entityServiceMock', ['getAllComponents']); + //entityServiceMock.getAllComponents.and.returnValue(getAllEntitiesDefered.promise); + + // $stateParams['show'] = ''; + + /** + * Need to inject into the controller only the objects that we want to MOCK + * those that we need to change theirs behaviors + */ + $controllerMock(Sdc.ViewModels.Wizard.PropertyFormViewModel, { + '$scope': $scopeMock, + 'property': new Sdc.Models.PropertyModel(), + 'component': component, + }); + + })); + + describe("when Controller 'PropertyFormViewModel' created", () => { + + it('should have a regexp per each type', () => { + $scopeMock.$apply(); + expect(Object.keys($scopeMock.listRegex).length).toBe($scopeMock.editPropertyModel["simpleTypes"].length); + }); + + it('should have equal regexps for map and list', () => { + $scopeMock.$apply(); + expect(Object.keys($scopeMock.listRegex).length).toBe(Object.keys($scopeMock.mapRegex).length); + }); + + }); + + /*describe("when Controller 'DashboardViewModel' created", () => { + + it('should generate all entities', () => { + $scopeMock.$apply(); + expect($scopeMock.components.length).toBe(getAllEntitiesResponseMock.length); + }); + + + it('should show tutorial page ', () => { + $stateParams.show = 'tutorial'; + + $controllerMock(Sdc.ViewModels.DashboardViewModel, { + '$scope': $scopeMock, + '$stateParams': $stateParams, + 'Sdc.Services.EntityService': entityServiceMock, + //to complete injects + }); + + $scopeMock.$apply(); + expect($scopeMock.isFirstTime).toBeTruthy(); + expect($scopeMock.showTutorial).toBeTruthy(); + }); + + }); + + + describe("when function 'entitiesCount' invoked", () => { + + beforeEach(() => { + $controllerMock(Sdc.ViewModels.DashboardViewModel, { + '$scope': $scopeMock, + 'Sdc.Services.EntityService': entityServiceMock, + }); + $scopeMock.$apply(); + }); + + it('should return entities count per folder', () => { + + }); + + + });*/ +}); diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts new file mode 100644 index 0000000000..5cb0ef1ddd --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts @@ -0,0 +1,250 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> + +module Sdc.ViewModels.Wizard { + 'use strict'; + + export interface IEditPropertyModel{ + property: Models.PropertyModel; + types: Array<string>; + simpleTypes: Array<string>; + sources: Array<string>; + } + + export interface IPropertyFormViewModelScope extends ng.IScope{ + + $$childTail: any; + editForm:ng.IFormController; + forms:any; + footerButtons: Array<any>; + isNew: boolean; + isLoading: boolean; + validationPattern: RegExp; + propertyNameValidationPattern: RegExp; + integerValidationPattern: RegExp; + floatValidationPattern: RegExp; + commentValidationPattern: RegExp; + listRegex: Sdc.Utils.IMapRegex; + mapRegex: Sdc.Utils.IMapRegex; + editPropertyModel: IEditPropertyModel; + modalInstanceProperty: ng.ui.bootstrap.IModalServiceInstance; + save(doNotCloseModal?: boolean): void; + saveAndAnother(): void; + getValidation(): RegExp; + validateIntRange(value:string):boolean; + close(): void; + onValueChange(): void; + onTypeChange(resetSchema:boolean): void; + showSchema(): boolean; + getValidationTranslate():string; + validateUniqueKeys(viewValue:string):boolean; + } + + export class PropertyFormViewModel{ + + private originalValue: string; + + static '$inject' = [ + '$scope', + '$modalInstance', + 'property', + 'ValidationPattern', + 'PropertyNameValidationPattern', + 'IntegerNoLeadingZeroValidationPattern', + 'FloatValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + 'component' + ]; + + private formState: Utils.Constants.FormState; + private entityId: string; + private resourceInstanceUniqueId: string; + private readonly: boolean; + + constructor( + private $scope:IPropertyFormViewModelScope, + private $modalInstance: ng.ui.bootstrap.IModalServiceInstance, + private property : Models.PropertyModel, + private ValidationPattern : RegExp, + private PropertyNameValidationPattern: RegExp, + private IntegerNoLeadingZeroValidationPattern : RegExp, + private FloatValidationPattern : RegExp, + private CommentValidationPattern: RegExp, + private ValidationUtils: Sdc.Utils.ValidationUtils, + private component:Models.Components.Component + ){ + this.entityId = this.component.uniqueId; + this.formState = angular.isDefined(property.name) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE; + this.initScope(); + } + + + private initResource = (): void => { + this.$scope.editPropertyModel.property = new Sdc.Models.PropertyModel(this.property); + this.originalValue = this.property.defaultValue; + if(this.$scope.editPropertyModel.types.indexOf(this.property.type) === -1 && !this.$scope.isNew){ + this.property.type = "string"; + } + }; + + private initEditPropertyModel = (): void => { + this.$scope.editPropertyModel = { + property: null, + types: ['integer', 'string', 'float', 'boolean', 'list', 'map'], + simpleTypes: ['integer', 'string', 'float', 'boolean'], + sources: ['A&AI', 'Order', 'Runtime'] + }; + + this.initResource(); + }; + + private initScope = (): void => { + + this.$scope.modalInstanceProperty = this.$modalInstance; + //scope properties + this.$scope.validationPattern = this.ValidationPattern; + this.$scope.propertyNameValidationPattern = this.PropertyNameValidationPattern; + this.$scope.integerValidationPattern = this.IntegerNoLeadingZeroValidationPattern; + this.$scope.floatValidationPattern = this.FloatValidationPattern; + this.$scope.commentValidationPattern = this.CommentValidationPattern; + + //map & list validation patterns + this.$scope.listRegex = this.ValidationUtils.getPropertyListPatterns(); + this.$scope.mapRegex = this.ValidationUtils.getPropertyMapPatterns(); + + this.$scope.isLoading = false; + this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE); + this.initEditPropertyModel(); + + //scope methods + this.$scope.save = (): void => { + this.$scope.editPropertyModel.property.description = this.ValidationUtils.stripAndSanitize(this.$scope.editPropertyModel.property.description); + this.$scope.isLoading = true; + + let onFailed = (response) => { + console.info('onFaild',response); + this.$scope.isLoading = false; + this.$scope.editPropertyModel.property.readonly = this.readonly; + this.$scope.editPropertyModel.property.resourceInstanceUniqueId = this.resourceInstanceUniqueId; + }; + + let onSuccess = (property: Models.PropertyModel): void => { + console.info('property added : ',property); + this.$scope.isLoading = false; + property.resourceInstanceUniqueId = this.resourceInstanceUniqueId; + property.readonly = (property.parentUniqueId !== this.component.uniqueId) /*|| this.component.isService()*/; + + this.$modalInstance.close(); + }; + + this.resourceInstanceUniqueId = this.$scope.editPropertyModel.property.resourceInstanceUniqueId; + this.readonly = this.$scope.editPropertyModel.property.readonly; + this.$scope.editPropertyModel.property.defaultValue = this.$scope.editPropertyModel.property.defaultValue ? this.$scope.editPropertyModel.property.defaultValue:null; + + this.component.addOrUpdateProperty(this.$scope.editPropertyModel.property).then(onSuccess, onFailed); + }; + + this.$scope.saveAndAnother = (): void => { + this.$scope.save(); + }; + + this.$scope.showSchema = () :boolean => { + return ['list', 'map'].indexOf(this.$scope.editPropertyModel.property.type) > -1; + }; + + this.$scope.getValidationTranslate = () : string => { + let result = "PROPERTY_EDIT_PATTERN"; + if (this.$scope.showSchema()) { + + result = "PROPERTY_EDIT_" + this.$scope.editPropertyModel.property.type.toUpperCase(); + + if(this.$scope.editPropertyModel.property.schema.property.type === 'string') { + result += "_STRING"; + } else { + result += "_GENERIC"; + } + } + + return result; + }; + + this.$scope.getValidation = () : RegExp => { + let type = this.$scope.editPropertyModel.property.type; + switch (type){ + case 'integer': + return this.$scope.integerValidationPattern; + case 'float': + return this.$scope.floatValidationPattern; + case 'list': + return this.$scope.listRegex[this.$scope.editPropertyModel.property.schema.property.type]; + case 'map': + return this.$scope.mapRegex[this.$scope.editPropertyModel.property.schema.property.type]; + default : + return null; + } + }; + + this.$scope.validateUniqueKeys = (viewValue:string) : boolean => { + if(this.$scope.editPropertyModel.property.type === 'map') { + return this.ValidationUtils.validateUniqueKeys(viewValue); + } + else { + return true; //always valid if not a map + } + }; + + this.$scope.validateIntRange = (value:string):boolean => { + return !value || this.ValidationUtils.validateIntRange(value); + }; + + this.$scope.close = (): void => { + this.$modalInstance.close(); + }; + + this.$scope.onValueChange = (): void => { + if(!this.$scope.editPropertyModel.property.defaultValue && this.$scope.editPropertyModel.property.required) { + this.$scope.editPropertyModel.property.defaultValue = this.originalValue; + } + }; + + this.$scope.onTypeChange = (resetSchema:boolean): void => { + this.$scope.editPropertyModel.property.defaultValue = ''; + if (resetSchema) { + this.$scope.editPropertyModel.property.schema.property.type = ''; + } + }; + + //new form layout for import asset + this.$scope.forms = {}; + this.$scope.footerButtons = [ + {'name': this.$scope.isNew ? 'Add' : 'Update', 'css':'blue', 'callback': this.$scope.save}, + {'name':'Cancel', 'css':'grey', 'callback': this.$scope.close} + ]; + + this.$scope.$watch('forms.editForm.$invalid', () => { + this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid; + }); + + } + + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html new file mode 100644 index 0000000000..be237112a4 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html @@ -0,0 +1,133 @@ +<sdc-modal modal="modalInstanceProperty" type="classic" class="sdc-add-property" buttons="footerButtons" header="{{isNew ? 'Add' : 'Update' }} Property" show-close-button="true"> + + <form novalidate class="w-sdc-form two-columns" name="forms.editForm" > + + <div class="w-sdc-form-columns-wrapper"> + + <div class="w-sdc-form-column"> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.propertyName.$dirty && forms.editForm.propertyName.$invalid)}"> + <label class="i-sdc-form-label" ng-class="{'required': !isService}">Name</label> + <input class="i-sdc-form-input" + data-ng-maxlength="50" + data-ng-disabled="!isNew || editPropertyModel.property.readonly" + maxlength="50" + data-ng-model="editPropertyModel.property.name" + type="text" + name="propertyName" + data-ng-pattern="propertyNameValidationPattern" + data-required + data-ng-model-options="{ debounce: 200 }" + data-tests-id="propertyName" + autofocus /> + + <div class="input-error" data-ng-show="forms.editForm.propertyName.$dirty && forms.editForm.propertyName.$invalid"> + <span ng-show="forms.editForm.propertyName.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Property name' }"></span> + <span ng-show="forms.editForm.propertyName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span> + <span ng-show="forms.editForm.propertyName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.schemaType.$dirty && forms.editForm.schemaType.$invalid)}" + data-ng-if="showSchema()"> + <label class="i-sdc-form-label required">Entry Schema</label> + <select class="i-sdc-form-select" + data-required + name="schemaType" + data-tests-id="schemaType" + data-ng-change="onTypeChange(false)" + data-ng-model="editPropertyModel.property.schema.property.type" + data-ng-options="type for type in editPropertyModel.simpleTypes"> + <option value="">Choose Schema Type</option> + </select> + + <div class="input-error" data-ng-show="forms.editForm.schemaType.$dirty && forms.editForm.schemaType.$invalid"> + <span ng-show="forms.editForm.schemaType.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Entry schema' }"></span> + </div> + </div> + + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.value.$dirty && forms.editForm.value.$invalid)}"> + <label class="i-sdc-form-label">Default Value</label> + <input class="i-sdc-form-input" + ng-if="!(editPropertyModel.property.type == 'boolean')" + data-ng-maxlength="100" + data-ng-disableddddddd="editPropertyModel.property.readonly && !isService && !isPropertyValueOwner()" + maxlength="100" + data-ng-model="editPropertyModel.property.defaultValue" + type="text" + name="value" + data-custom-validation="" data-validation-func="validateUniqueKeys" + data-ng-pattern="getValidation()" + data-ng-model-options="{ debounce: 200 }" + data-ng-change="!forms.editForm.value.$error.pattern && ('integer'==editPropertyModel.property.type && forms.editForm.value.$setValidity('pattern', validateIntRange(editPropertyModel.property.defaultValue)) || onValueChange())" + data-tests-id="defaultValue" + autofocus /> + <select class="i-sdc-form-select" + ng-if="editPropertyModel.property.type == 'boolean'" + data-ng-disabled="editPropertyModel.property.readonly && !isPropertyValueOwner()" + name="value" + data-ng-change="onValueChange()" + data-ng-model="editPropertyModel.property.defaultValue"> + <option value=""></option> + <option value="true">true</option> + <option value="false">false</option> + </select> + + <div class="input-error" data-ng-show="forms.editForm.value.$dirty && forms.editForm.value.$invalid"> + <span ng-show="forms.editForm.value.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Property' }"></span> + <span ng-show="forms.editForm.value.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '100' }"></span> + <span ng-show="forms.editForm.value.$error.pattern" translate="{{getValidationTranslate()}}"></span> + <span ng-show="forms.editForm.value.$error.customValidation" translate="PROPERTY_EDIT_MAP_UNIQUE_KEYS"></span> + </div> + </div> + + </div> + + <div class="w-sdc-form-column"> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}"> + <label class="i-sdc-form-label" ng-class="{'required': !isService}">Type</label> + <select class="i-sdc-form-select" + data-required + data-ng-disableddddddd="editPropertyModel.property.readonly" + data-tests-id="propertyType" + name="type" + data-ng-change="onTypeChange(true)" + data-ng-model="editPropertyModel.property.type" + data-ng-options="type for type in editPropertyModel.types"> + <option value="">Choose Type</option> + </select> + + <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid"> + <span ng-show="forms.editForm.type.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Type' }"></span> + </div> + </div> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}"> + <label class="i-sdc-form-label">Description</label> + <textarea class="i-sdc-form-textarea" + data-ng-maxlength="256" + data-ng-disableddddddd="editPropertyModel.property.readonly" + maxlength="256" + data-ng-pattern="commentValidationPattern" + name="description" + data-ng-model="editPropertyModel.property.description" + data-ng-model-options="{ debounce: 200 }" + data-tests-id="description" + ></textarea> + + <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid"> + <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span> + <span ng-show="forms.editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="forms.editForm.description.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Description' }"></span> + </div> + </div> + + </div> + + </div> + </form> + +</sdc-modal> diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less new file mode 100644 index 0000000000..52b8564fdb --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less @@ -0,0 +1,7 @@ +.sdc-add-property{ + + .w-sdc-form { + + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html new file mode 100644 index 0000000000..afa9307265 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html @@ -0,0 +1,12 @@ +<loader data-display="isLoading"></loader> +<div class="sdc-wizard-name-type-label" data-ng-if="assetName!==undefined"><span class="sprite sprite-green-tick animated flash"></span><span class="name">{{assetName}}</span> | <span class="type">{{assetType}}</span></div> +<sdc-modal modal="modalInstance" type="classic" get-close-modal-response="getComponent" buttons="footerButtons" class="sdc-wizard" header="{{modalTitle}}" show-close-button="true"> + <div class="sdc-wizard-wrapper"> + <div class="sdc-wizard-left-content"> + <sdc-wizard-step steps="directiveSteps" control="assetCreationControl" class="wizard-steps-line"></sdc-wizard-step> + </div> + <div class="sdc-wizard-right-content"> + <ng-include src="templateUrl" class="sdc-wizard-right-include"></ng-include> + </div> + </div> +</sdc-modal> diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less new file mode 100644 index 0000000000..591186789b --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less @@ -0,0 +1,60 @@ +.sdc-wizard-wrapper { + display: flex; + padding: 80px 0; + height: 620px; + // So input validation error will be shown outside the modal (long messages). + // overflow: hidden; + + .sdc-wizard-left-content { + width: 400px; + white-space: nowrap; + + .wizard-steps-line { + padding: 0 80px 0 0; + display: block; + } + + } + + .sdc-wizard-right-content { + margin-right: 200px; + /* background-color: #fafafa; */ + height: 100%; + width: 100%; + .perfect-scrollbar; + overflow: visible; + //.animation-duration(2s); + + .sdc-wizard-right-include { + + } + } + +} + +.sprite-green-tick.animated { + .animation-duration(3s); +} + +.sdc-wizard-name-type-label { + position: absolute; + top: 64px; + right: 40px; + + span { + .b_7; + &.name {.bold;} + &.sprite-green-tick { + position: absolute; + left: -22px; + top: 5px; + } + } + +} + +.w-wizard-footer { + button.cancel { + margin-right: 120px; + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts new file mode 100644 index 0000000000..365d3aedf6 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts @@ -0,0 +1,399 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + export class StepNames { + static general = "General"; + static icon = "Icon"; + static deploymentArtifact = "Deployment Artifact"; + static informationArtifact = "Information Artifact"; + static properties = "Properties"; + static hierarchy = "Hierarchy"; + } + + class FooterButtons { + static cancel = "Cancel"; + static back = "Back"; + static next = "Next"; + static finish = "Finish"; + } + + export class WizardCreationTypes { + static importAsset = "importAsset"; + static create = "create"; + static edit = "edit"; + } + + export class _CreationStep { + name:string; + url:string; + } + + export class CurrentStep { + assetCreationStep:_CreationStep; + reference:IWizardCreationStep; + index:number; + valid: boolean; + } + + export interface IWizardCreationStep { + save(callback:Function):void; + back(callback:Function):void; + } + + export interface IWizardCreationStepScope extends ng.IScope { + data:any; + getComponent():Sdc.Models.Components.Component; + setComponent(component: Sdc.Models.Components.Component):void; + registerChild(child:IWizardCreationStep):void; // Called from the step + setValidState(valid:boolean):void; // Called from the step + } + + export interface IWizardCreationScope extends ng.IScope { + isLoading: boolean; + data:any; // data passed from dashboard (opener), need it on the scope, because general step will use this (extends the scope) + directiveSteps: Array<Sdc.Directives.IWizardStep>; // Steps for the directive, on the scope (on the scope because need to pass to directive via HTML) + templateUrl: string; // On the scope because need to pass to <ng-include> via HTML + footerButtons: Array<Sdc.Directives.ISdcModalButton>; // Wizard footer buttons (on the scope because need to pass to directive via HTML) + assetCreationControl:any; // Link to wizard directive functions. + modalInstance:ng.ui.bootstrap.IModalServiceInstance; // Reference to the modal, so we can close it (on the scope because need to pass to directive via HTML) + assetName:string; + assetType:string; + modalTitle:string; + getComponent():Sdc.Models.Components.Component; + setComponent(component: Sdc.Models.Components.Component):void; + registerChild(child:IWizardCreationStep):void; // Called from the step + setValidState(valid:boolean):void; // Called from the step + } + + export class WizardCreationBaseViewModel { + + component: Sdc.Models.Components.Component; + protected assetCreationSteps: Array<_CreationStep>; // Contains URL and name so we can replace them + currentStep:CurrentStep; + protected type:string; + + constructor(protected $scope:IWizardCreationScope, + protected data:any, + protected ComponentFactory: Utils.ComponentFactory, + protected $modalInstance: ng.ui.bootstrap.IModalServiceInstance + ) { + + this.$scope.data = data; + this.currentStep = new CurrentStep(); + this.currentStep.valid=false; + this.$scope.modalInstance = this.$modalInstance; + this.initScope(); + this.noBackspaceNav(); + + // In case the modal was opened with filled resource (edit mode). + if (data.component){ + this.$scope.setComponent(data.component); + data.componentType = this.$scope.getComponent().componentType; + window.setTimeout(()=>{ + this.safeApply(this.setCurrentStepByIndex(0, false)); + },100); + } else { + // Default step to start with + window.setTimeout(()=>{ + this.safeApply(this.setCurrentStepByIndex(0, false)); + },100); + } + + } + + private safeApply = (fn:any) => { + let phase = this.$scope.$root.$$phase; + if (phase == '$apply' || phase == '$digest') { + if (fn && (typeof(fn) === 'function')) { + fn(); + } + } else { + this.$scope.$apply(fn); + } + }; + + private initScope = ():void => { + + // Control to call functions on wizard step directive + this.$scope.assetCreationControl = {}; + + // Footer buttons definitions for the modal directive. + this.$scope.footerButtons = [ + {"name":FooterButtons.cancel, "css":'white cancel',"callback": ()=>{this.btnCancelClicked();}}, + {"name":FooterButtons.back, "disabled":true, "css":'white back',"callback": ()=>{this.btnBackClicked();}}, + {"name":FooterButtons.next, "disabled":true, "css":'blue next',"callback": ()=>{this.btnNextClicked();}}, + {"name":FooterButtons.finish, "disabled":true, "css":'white finish',"callback": ()=>{this.btnFinishedClicked();}} + ]; + + // Will be called from step constructor to register him. + // So the current step will be the reference. + this.$scope.registerChild=(child:IWizardCreationStep):void => { + this.currentStep.reference=child; + }; + + // Will be called from each step to notify if the step is valid + // The wizard will set the "Next", "Finish" buttons accordingly. + this.$scope.setValidState = (valid:boolean):void => { + this.currentStep.valid=valid; + let currentDirectiveStep:Sdc.Directives.IWizardStep = this.$scope.directiveSteps[this.currentStep.index]; + this.$scope.assetCreationControl.setStepValidity(currentDirectiveStep, valid); + this.footerButtonsStateMachine(); + this.wizardButtonsIconsStateMachine(); + }; + + /** + * Will be called from each step to get current entity. + * This will return copy of the entity (not reference), because I do not want that the step will change entity parameters. + * If the step need to update the entity it can call setComponent function. + * @returns {Sdc.Models.IEntity} + */ + this.$scope.getComponent = ():Sdc.Models.Components.Component => { + return this.component; + }; + + // Will be called from each step after save to update the resource. + this.$scope.setComponent = (component:Sdc.Models.Components.Component):void => { + this.component = component; + }; + + }; + + protected setCurrentStepByName = (stepName:string):boolean => { + let stepIndex:number = this.getStepIndex(stepName); + return this.setCurrentStepByIndex(stepIndex); + }; + + // Set the current step, change the URL in ng-include. + protected setCurrentStepByIndex = (index:number, doSave:boolean=true):boolean => { + let result:boolean = false; + if (this.currentStep.index!==index) { // Check that not pressing on same step, also the first time currentStepIndex=undefined + if (doSave===true) { + this.callStepSave(() => { + // This section will be executed only if success save. + + // Set current step in the left wizard directive = valid + let currentDirectiveStep:Sdc.Directives.IWizardStep = this.$scope.directiveSteps[this.currentStep.index]; + this.$scope.assetCreationControl.setStepValidity(currentDirectiveStep, true); + + // Move to next step + let step:_CreationStep = this.assetCreationSteps[index]; + this.currentStep.index = index; + this.currentStep.assetCreationStep = step; + this.$scope.templateUrl = step.url; + + // Update the next/back buttons and steps buttons. + this.footerButtonsStateMachine(); + this.wizardButtonsIconsStateMachine(); + + // Can not perform step click without enabling the step + this.$scope.directiveSteps[index].enabled = true; // Need to set the step enabled, before clicking it. + this.$scope.assetCreationControl.stepClicked(step.name); + + // After saving the asset name and type will be shown in the top right of the screen. + this.fillAssetNameAndType(); + + result=true; + }); + } else { + // For the first time + let step:_CreationStep = this.assetCreationSteps[index]; + this.currentStep.index = index; + this.currentStep.assetCreationStep=step; + this.$scope.templateUrl = step.url; + this.$scope.directiveSteps[index].enabled = true; // Need to set the step enabled, before clicking it. + this.$scope.assetCreationControl.stepClicked(step.name); + result=true; + } + + //this.updateFooterButtonsStates(); + + } else { + result=true; + } + return result; + }; + + // Save the current step + private callStepSave = (successCallback:Function):void => { + this.$scope.isLoading = true; + this.currentStep.reference.save((result:boolean)=>{ + this.$scope.isLoading = false; + if (result===true){ + successCallback(); + } else { + // Set the next and finish button enabled. + //this.updateFooterButtonsStates(true); + } + }); + }; + + // Save the current step + private callStepBack = (successCallback:Function):void => { + this.$scope.isLoading = true; + this.currentStep.reference.back((result:boolean)=>{ + this.$scope.isLoading = false; + if (result===true){ + successCallback(); + } else { + // Set the next and finish button enabled. + //this.updateFooterButtonsStates(true); + } + }); + }; + + private getStepIndex = (stepName:string):number => { + let index:number=-1; + let tmp = _.find(this.assetCreationSteps, function (item, indx) { + index = indx; + return item.name === stepName; + }); + return index; + }; + + private btnNextClicked = ():void => { + if (this.hasNext()===true) { + let tmp = this.currentStep.index+1; + this.setCurrentStepByIndex(tmp); + } + }; + + private btnBackClicked = ():void => { + if (this.hasBack()===true) { + this.callStepBack(() => { + let tmp = this.currentStep.index-1; + this.setCurrentStepByIndex(tmp, false); + }); + } + }; + + private btnCancelClicked = ():void => { + this.$modalInstance.dismiss(this.$scope.getComponent()); + }; + + private btnFinishedClicked = ():void => { + this.callStepSave(() => { + this.$modalInstance.close(this.$scope.getComponent()); + }); + }; + + // Check if we can move next + private hasNext = ():boolean => { + if (this.assetCreationSteps.length-1>this.currentStep.index){ + return true; + } else { + return false; + } + }; + + // Check if we can move back + private hasBack = ():boolean => { + if (this.currentStep.index===0){ + return false; + } else { + return true; + } + }; + + private fillAssetNameAndType=():void => { + this.$scope.assetName = this.$scope.getComponent().name; + this.$scope.assetType = this.$scope.getComponent().getComponentSubType(); + + }; + + protected enableAllWizardSteps=():void => { + this.$scope.directiveSteps.forEach((step:Sdc.Directives.IWizardStep) => { + step.enabled=true; + }); + }; + + protected disableAllWizardSteps=():void => { + this.$scope.directiveSteps.forEach((step:Sdc.Directives.IWizardStep) => { + step.enabled=false; + }); + }; + + private footerButtonsStateMachine = ():void => { + //console.log("footerButtonsStateMachine, current step validity: " + this.currentStep.valid); + let stepIndex:number = this.currentStep.index; + let cancelButton = this.$scope.footerButtons[0]; + let backButton = this.$scope.footerButtons[1]; + let nextButton = this.$scope.footerButtons[2]; + let finishButton = this.$scope.footerButtons[3]; + + // NEXT button + // Disable next button if it is the last step, and if not check the validity of the step. + if (this.hasNext()){ + nextButton.disabled = !this.currentStep.valid; + } else { + nextButton.disabled = true; + } + + // BACK button + backButton.disabled = !this.hasBack(); + + // FINISH button + // If step 2 is valid show the finish button. + if (stepIndex>=1 && this.currentStep.valid===true) { + finishButton.disabled = false; + } + if (this.currentStep.valid===false){ + finishButton.disabled = true; + } + + // EDIT + if (this.type===WizardCreationTypes.edit && this.currentStep.valid===true){ + finishButton.disabled = false; + } + + }; + + + + private wizardButtonsIconsStateMachine = ():void => { + + // Enable or disable wizard directive next step, in case the current step form is valid or not. + let stepIndex:number = this.currentStep.index; + if (this.$scope.directiveSteps[stepIndex + 1]) { + this.$scope.directiveSteps[stepIndex + 1].enabled = this.currentStep.valid; + } + + // In case step 1 and 2 are valid, we can open all other steps. + if (this.$scope.directiveSteps[0].valid===true && this.$scope.directiveSteps[1].valid===true){ + // Enable all wizard directive steps + this.enableAllWizardSteps(); + } else if (this.currentStep.valid===false) { + // Disable all steps + this.disableAllWizardSteps(); + } + }; + + private noBackspaceNav:Function = ():void => { + this.$scope.$on('$locationChangeStart', (event, newUrl, oldUrl):void =>{ + event.preventDefault(); + }) + }; + + + } + +} diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts new file mode 100644 index 0000000000..9490cddfdb --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts @@ -0,0 +1,114 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/** + * Created by obarda on 3/15/2016. + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + export class CreateWizardViewModel extends WizardCreationBaseViewModel { + + static '$inject' = [ + '$scope', + 'data', + 'ComponentFactory', + '$modalInstance' + ]; + + constructor(public $scope:IWizardCreationScope, + public data:any, + public ComponentFactory: Sdc.Utils.ComponentFactory, + public $modalInstance: ng.ui.bootstrap.IModalServiceInstance) { + + super($scope, data, ComponentFactory, $modalInstance); + this.type = WizardCreationTypes.create; + this.init(); + this.initCreateAssetScope(); + } + + private init = ():void => { + + switch (this.data.componentType){ + case Utils.Constants.ComponentType.RESOURCE: { + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}, + {"name": StepNames.deploymentArtifact, "url": '/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html'}, + {"name": StepNames.informationArtifact, "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'}, + {"name": StepNames.properties, "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'} + ]; + } + break; + + case Utils.Constants.ComponentType.SERVICE: { + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'} + ]; + } + break; + + case Utils.Constants.ComponentType.PRODUCT: { + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.hierarchy, "url": '/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'} + ]; + } + break; + } + }; + + private initCreateAssetScope = ():void => { + switch (this.data.componentType){ + case Utils.Constants.ComponentType.RESOURCE: { + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}, + {"name": StepNames.deploymentArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.deploymentArtifact);}}, + {"name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.informationArtifact);}}, + {"name": StepNames.properties, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.properties);}} + ]; + this.$scope.modalTitle = "Create VF"; + break; + } + case Utils.Constants.ComponentType.SERVICE: { + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}, + + ]; + this.$scope.modalTitle = "Create Service"; + break; + } + case Utils.Constants.ComponentType.PRODUCT: { + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.hierarchy, "enabled":false, "callback": ()=> {return this.setCurrentStepByName(StepNames.hierarchy);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}} + ]; + this.$scope.modalTitle = "Create Product"; + break; + } + } + } + } +} diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts new file mode 100644 index 0000000000..353c487e0a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts @@ -0,0 +1,164 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + export class EditWizardViewModel extends WizardCreationBaseViewModel { + + static '$inject' = [ + '$scope', + 'data', + 'ComponentFactory', + '$modalInstance' + ]; + + constructor(public $scope:IWizardCreationScope, + public data:any, + public ComponentFactory: Sdc.Utils.ComponentFactory, + public $modalInstance: ng.ui.bootstrap.IModalServiceInstance) { + + super($scope, data, ComponentFactory, $modalInstance); + this.type = WizardCreationTypes.edit; + this.init(); + this.initCreateAssetScope(); + + // Enable all wizard directive steps + this.enableAllWizardSteps(); + } + + private init = ():void => { + switch (this.data.component.componentType){ + case Utils.Constants.ComponentType.RESOURCE: { + if(this.data.component.isComplex()) { + this.assetCreationSteps = [ + { + "name": StepNames.general, + "url": '/app/scripts/view-models/wizard/general-step/general-step.html' + }, + { + "name": StepNames.icon, + "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html' + }, + { + "name": StepNames.deploymentArtifact, + "url": '/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html' + }, + { + "name": StepNames.informationArtifact, + "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html' + }, + { + "name": StepNames.properties, + "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html' + } + ]; + }else{ + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}, + {"name": StepNames.informationArtifact, "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'}, + {"name": StepNames.properties, "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'} + ]; + } + break; + } + case Utils.Constants.ComponentType.SERVICE: { + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'} + ]; + break; + } + case Utils.Constants.ComponentType.PRODUCT: { + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.hierarchy, "url": '/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'} + ]; + break; + } + } + }; + + private initCreateAssetScope = ():void => { + switch (this.data.component.componentType){ + case Utils.Constants.ComponentType.RESOURCE: { + if(this.data.component.isComplex()) { + this.$scope.directiveSteps = [ + { + "name": StepNames.general, "enabled": true, "callback": ()=> { + return this.setCurrentStepByName(StepNames.general); + } + }, + { + "name": StepNames.icon, "enabled": false, "callback": ()=> { + return this.setCurrentStepByName(StepNames.icon); + } + }, + { + "name": StepNames.deploymentArtifact, "enabled": false, "callback": ()=> { + return this.setCurrentStepByName(StepNames.deploymentArtifact); + } + }, + { + "name": StepNames.informationArtifact, "enabled": false, "callback": ()=> { + return this.setCurrentStepByName(StepNames.informationArtifact); + } + }, + { + "name": StepNames.properties, "enabled": false, "callback": ()=> { + return this.setCurrentStepByName(StepNames.properties); + } + } + ]; + }else{ + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}, + {"name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.informationArtifact);}}, + {"name": StepNames.properties, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.properties);}} + ]; + } + this.$scope.modalTitle = "Edit " + this.data.component.resourceType; + break; + } + case Utils.Constants.ComponentType.SERVICE: { + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}} + ]; + this.$scope.modalTitle = "Edit Service"; + break; + } + case Utils.Constants.ComponentType.PRODUCT: { + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.hierarchy, "enabled":false, "callback": ()=> {return this.setCurrentStepByName(StepNames.hierarchy);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}} + ]; + this.$scope.modalTitle = "Edit Product"; + break; + } + } + } + } +} + diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts new file mode 100644 index 0000000000..5fe1bf7e59 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts @@ -0,0 +1,64 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../references"/> +module Sdc.ViewModels.Wizard { + 'use strict'; + + export class ImportWizardViewModel extends WizardCreationBaseViewModel { + + static '$inject' = [ + '$scope', + 'data', + 'ComponentFactory', + '$modalInstance' + ]; + + constructor(public $scope:IWizardCreationScope, + public data:any, + public ComponentFactory: Sdc.Utils.ComponentFactory, + public $modalInstance: ng.ui.bootstrap.IModalServiceInstance) { + + super($scope, data, ComponentFactory, $modalInstance ); + this.type = WizardCreationTypes.importAsset; + this.init(); + this.initImportAssetScope(); + } + + private init = ():void => { + this.assetCreationSteps = [ + {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'}, + {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}, + {"name": StepNames.informationArtifact, "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'}, + {"name": StepNames.properties, "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'} + ]; + }; + + private initImportAssetScope = ():void => { + this.$scope.directiveSteps = [ + {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}}, + {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}, + {"name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.informationArtifact);}}, + {"name": StepNames.properties, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.properties);}} + ]; + + this.$scope.modalTitle = "Import Asset"; + }; + } +} |