diff options
author | Michael Lando <ml636r@att.com> | 2018-03-04 14:53:33 +0200 |
---|---|---|
committer | Michael Lando <ml636r@att.com> | 2018-03-07 13:19:05 +0000 |
commit | a5445100050e49e83f73424198d73cd72d672a4d (patch) | |
tree | cacf4df817df31be23e4e790d1dda857bdae061e /catalog-ui/src/app/ng2/pages | |
parent | 51157f92c21976cba4914c378aaa3cba49826931 (diff) |
Sync Integ to Master
Change-Id: I71e3acc26fa612127756ac04073a522b9cc6cd74
Issue-ID: SDC-977
Signed-off-by: Gitelman, Tal (tg851x) <tg851x@intl.att.com>
Diffstat (limited to 'catalog-ui/src/app/ng2/pages')
26 files changed, 972 insertions, 151 deletions
diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.service.ts b/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.service.ts index a097fb04ea..bf5ec4c4f6 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.service.ts +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.service.ts @@ -1,3 +1,4 @@ +import * as _ from "lodash"; import {ConnectRelationModel} from "../../../models/graph/connectRelationModel"; import {Injectable} from "@angular/core"; import { Requirement, Capability} from "app/models"; diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.html b/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.html index 0c9d9e6e26..a45f07b3e2 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.html +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.html @@ -3,6 +3,5 @@ [selectedReqOrCapModel]="connectWizardService.selectedMatch && (connectWizardService.selectedMatch.isFromTo ? connectWizardService.selectedMatch.requirement : connectWizardService.selectedMatch.capability)" [currentComponent]="connectWizardService.currentComponent" [componentInstanceId]="connectWizardService.connectRelationModel.fromNode.componentInstance.uniqueId" - (updateSelectedReqOrCap)="updateSelectedReqOrCap($event)" - (updateCapabilityProperties)="onCapabilityPropertiesUpdate($event)"> + (updateSelectedReqOrCap)="updateSelectedReqOrCap($event)"> </select-requirement-or-capability>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.ts b/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.ts index edbbf8a0a3..054d38b063 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.ts +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.ts @@ -32,10 +32,6 @@ export class FromNodeStepComponent implements IStepComponent, OnInit{ return true; } - onCapabilityPropertiesUpdate(capabilityProperties:Array<PropertyModel>) { - this.connectWizardService.selectedMatch.capabilityProperties = capabilityProperties; - } - private updateSelectedReqOrCap = (selected:Requirement|Capability):void => { if(!selected){ this.connectWizardService.selectedMatch = null; diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.html b/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.html index 9e34893272..9b1df69d77 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.html +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.html @@ -5,7 +5,7 @@ </div> <div class="properties-table-container"> <properties-table class="properties-table" - (valueChanged)="propertyValueChanged($event)" + (propertyChanged)="propertyValueChanged($event)" [fePropertiesMap]="capabilityPropertiesMap" [selectedPropertyId]="''" [hidePropertyType]="true"> diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.ts b/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.ts index 3e48785a3c..946d1858dc 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.ts +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.ts @@ -25,7 +25,7 @@ export class PropertiesStepComponent implements IStepComponent{ constructor(@Inject(forwardRef(() => ConnectionWizardService)) public connectWizardService: ConnectionWizardService, private componentInstanceServiceNg2:ComponentInstanceServiceNg2, private propertiesUtils:PropertiesUtils) { - this.capabilityPropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren({'capability' : connectWizardService.selectedMatch.capabilityProperties}, false); + this.capabilityPropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren({'capability' : connectWizardService.selectedMatch.capability.properties}, false); } ngOnInit() { @@ -42,7 +42,8 @@ export class PropertiesStepComponent implements IStepComponent{ propertyValueChanged = (property: PropertyFEModel) => { if (!property.isDeclared) { const propChangedIdx = this.connectWizardService.changedCapabilityProperties.indexOf(property); - if (this.componentInstanceServiceNg2.hasPropertyChanged(property)) { + if (property.hasValueObjChanged()) { + // if (this.componentInstanceServiceNg2.hasPropertyChanged(property)) { console.log("==>" + this.constructor.name + ": propertyValueChanged " + property); if (propChangedIdx === -1) { this.connectWizardService.changedCapabilityProperties.push(property); diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.html b/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.html index 67bb12e6fc..a8343ce7e2 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.html +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.html @@ -4,6 +4,5 @@ [selectedReqOrCapOption]="displayRequirementsOrCapabilities" [currentComponent]="connectWizardService.currentComponent" [componentInstanceId]="connectWizardService.connectRelationModel.toNode.componentInstance.uniqueId" - (updateSelectedReqOrCap)="updateSelectedReqOrCap($event)" - (updateCapabilityProperties)="onCapabilityPropertiesUpdate($event)"> + (updateSelectedReqOrCap)="updateSelectedReqOrCap($event)"> </select-requirement-or-capability>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.ts b/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.ts index 9c7bf4dfe6..ea3b129c7b 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.ts +++ b/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.ts @@ -43,10 +43,6 @@ export class ToNodeStepComponent implements IStepComponent{ return false; } - onCapabilityPropertiesUpdate(capabilityProperties:Array<PropertyModel>) { - this.connectWizardService.selectedMatch.capabilityProperties = capabilityProperties; - } - private updateSelectedReqOrCap = (selected:Requirement|Capability):void => { if (!selected) { if (this.connectWizardService.selectedMatch.isFromTo) { diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts index 203c75dd11..907f7638bb 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts @@ -32,6 +32,7 @@ import {DataTypeService} from "../../services/data-type.service"; import {PropertiesAssignmentComponent} from "./properties-assignment.page.component"; import {HierarchyNavService} from "./services/hierarchy-nav.service"; import {PropertiesUtils} from "./services/properties.utils"; +import {InputsUtils} from "./services/inputs.utils"; import {ComponentModeService} from "../../services/component-services/component-mode.service"; @NgModule({ @@ -53,7 +54,7 @@ import {ComponentModeService} from "../../services/component-services/component- exports: [ PropertiesAssignmentComponent ], - providers: [PropertiesService, HierarchyNavService, PropertiesUtils, DataTypeService, ComponentModeService] + providers: [PropertiesService, HierarchyNavService, PropertiesUtils, InputsUtils, DataTypeService, ComponentModeService] }) export class PropertiesAssignmentModule { diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html index beea3fe73f..03e9f1fa7e 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html @@ -1,43 +1,50 @@ <div class="properties-assignment-page"> <div class="main-content"> <div class="left-column"> - <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true"> - <tab tabTitle="Properties"> - <properties-table class="properties-table" - [fePropertiesMap]="instanceFePropertiesMap" - [feInstanceNamesMap]="componentInstanceNamesMap" - [searchTerm]="searchQuery" - [selectedPropertyId]="selectedFlatProperty.path" - [propertyNameSearchText]="searchPropertyName" - [readonly]="isReadonly" - [isLoading]="loadingProperties" - [hasDeclareOption]="true" - (valueChanged)="propertyValueChanged($event)" - (propertySelected)="propertySelected($event)" - (selectPropertyRow)="selectPropertyRow($event)" - (selectChildProperty)="selectChildProperty($event)" - (updateCheckedPropertyCount)="updateCheckedPropertyCount($event)" - (selectInstanceRow)="selectInstanceRow($event)"> + <div class="main-tabs-section"> + <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true"> + <tab tabTitle="Properties"> + <properties-table class="properties-table" + [fePropertiesMap]="instanceFePropertiesMap" + [feInstanceNamesMap]="componentInstanceNamesMap" + [searchTerm]="searchQuery" + [selectedPropertyId]="selectedFlatProperty.path" + [propertyNameSearchText]="searchPropertyName" + [readonly]="isReadonly" + [isLoading]="loadingProperties || savingChangedData" + [hasDeclareOption]="true" + (propertyChanged)="dataChanged($event)" + (propertySelected)="propertySelected($event)" + (selectPropertyRow)="selectPropertyRow($event)" + (selectChildProperty)="selectChildProperty($event)" + (updateCheckedPropertyCount)="updateCheckedPropertyCount($event)" + (selectInstanceRow)="selectInstanceRow($event)"> </properties-table> - </tab> - <tab tabTitle="Inputs"> - <inputs-table class="properties-table" - [readonly]="isReadonly" - [inputs]="inputs | searchFilter:'name':searchQuery" - [instanceNamesMap]="componentInstanceNamesMap" - [isLoading]="loadingInputs" - (deleteInput)="deleteInput($event)" - (inputValueChanged)="inputValueChanged($event)"></inputs-table> - </tab> - </tabs> + </tab> + <tab tabTitle="Inputs"> + <inputs-table class="properties-table" + [readonly]="isReadonly" + [inputs]="inputs | searchFilter:'name':searchQuery" + [instanceNamesMap]="componentInstanceNamesMap" + [isLoading]="loadingInputs" + (deleteInput)="deleteInput($event)" + (inputChanged)="dataChanged($event)"> + </inputs-table> + </tab> + </tabs> + <div class="main-tabs-section-buttons"> + <button class="tlv-btn outline blue" [disabled]="!hasChangedData || savingChangedData" (click)="reverseChangedData()" data-tests-id="properties-reverse-button">Discard</button> + <button class="tlv-btn blue" [disabled]="!hasChangedData || !isValidChangedData || savingChangedData" (click)="doSaveChangedData()" data-tests-id="properties-save-button">{{savingChangedData ? 'Saving' : 'Save'}}</button> + </div> + </div> <div class="header"> - <div class="search-filter-container" [class.without-filter]="isInpusTabSelected"> - <span *ngIf="displayClearSearch && !isInpusTabSelected" (click)="clickOnClearSearch()" class="clear-filter" data-tests-id="clear-filter-button">Clear All</span> + <div class="search-filter-container" [class.without-filter]="isInputsTabSelected"> + <span *ngIf="displayClearSearch && isPropertiesTabSelected" (click)="clickOnClearSearch()" class="clear-filter" data-tests-id="clear-filter-button">Clear All</span> <input type="text" class="search-box" placeholder="Search" [(ngModel)]="searchQuery" data-tests-id="search-box"/> <span class="sprite search-icon" data-tests-id="search-button"></span> - <filter-properties-assignment *ngIf="!isInpusTabSelected" #advanceSearch class="advance-search" [componentType]="component.componentType" (searchProperties)="searchPropertiesInstances($event)"></filter-properties-assignment> + <filter-properties-assignment *ngIf="isPropertiesTabSelected" #advanceSearch class="advance-search" [componentType]="component.componentType" (searchProperties)="searchPropertiesInstances($event)"></filter-properties-assignment> </div> - <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly" (click)="declareProperties()" data-tests-id="declare-button">Declare</button> + <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData" (click)="declareProperties()" data-tests-id="declare-button">Declare</button> </div> </div> <div class="right-column gray-border"> @@ -48,10 +55,10 @@ <div class="hierarchy-header white-sub-header"> <span tooltip="{{component.name}}">{{component.name}}</span> </div> - <div *ngIf="!instancesNavigationData || instancesNavigationData.length === 0 || isInpusTabSelected">No data to display</div> + <div *ngIf="!instancesNavigationData || instancesNavigationData.length === 0 || isInputsTabSelected">No data to display</div> <hierarchy-navigation class="hierarchy-nav" (updateSelected)="onInstanceSelectedUpdate($event)" - [displayData]="isInpusTabSelected ? []: instancesNavigationData" + [displayData]="isInputsTabSelected ? []: instancesNavigationData" [selectedItem]="selectedInstanceData.uniqueId" [displayOptions]="hierarchyInstancesDisplayOptions"></hierarchy-navigation> </div> @@ -59,12 +66,12 @@ <tab tabTitle="Property Structure"> <div class="hierarchy-nav-container"> <div class="hierarchy-header white-sub-header" [class.selected]="selectedFlatProperty.path == propertyStructureHeader"> - <span tooltip="{{!isInpusTabSelected ? propertyStructureHeader : ''}}">{{!isInpusTabSelected ? (propertyStructureHeader || "No Property Selected") : "No Property Selected"}}</span> + <span tooltip="{{isPropertiesTabSelected ? propertyStructureHeader : ''}}">{{isPropertiesTabSelected ? (propertyStructureHeader || "No Property Selected") : "No Property Selected"}}</span> </div> - <div *ngIf="!propertiesNavigationData || propertiesNavigationData.length === 0 || isInpusTabSelected">No data to display</div> + <div *ngIf="!propertiesNavigationData || propertiesNavigationData.length === 0 || isInputsTabSelected">No data to display</div> <hierarchy-navigation class="hierarchy-nav" (updateSelected)="onPropertySelectedUpdate($event)" - [displayData]="isInpusTabSelected ? [] : propertiesNavigationData" + [displayData]="isInputsTabSelected ? [] : propertiesNavigationData" [selectedItem]="selectedFlatProperty.path" [displayOptions]="hierarchyPropertiesDisplayOptions"></hierarchy-navigation> </div> @@ -72,4 +79,9 @@ </tabs> </div> </div> + <template #saveChangedDataModalContentTemplate> + <loader [display]="savingChangedData" [size]="'medium'" [relative]="true"></loader> + Your changes{{isValidChangedData ? '' : ' (invalid)'}} have not been saved.<br> + Do you want to {{isValidChangedData ? 'save' : 'discard'}} them? + </template> </div> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less index 03974bf723..6de6dda7bb 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less @@ -76,6 +76,7 @@ outline: none; font-style: italic; color:@ng2-med-dark-gray; + width: auto; &::-moz-placeholder { color:@ng2-med-dark-gray;} &::-webkit-input-placeholder{ color:@ng2-med-dark-gray;} @@ -113,6 +114,17 @@ top: 0; right: 0; } + + .main-tabs-section { + position: relative; + + .main-tabs-section-buttons { + position: absolute; + top: 45px; + right: 0; + padding: 4px; + } + } } .right-column { diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts index 9603648bd8..40818bce78 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts @@ -18,11 +18,11 @@ * ============LICENSE_END========================================================= */ -import {Component, ViewChild, ElementRef, Renderer, Inject} from "@angular/core"; +import * as _ from "lodash"; +import {Component, ViewChild, Inject, TemplateRef} from "@angular/core"; import { PropertiesService } from "../../services/properties.service"; -import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData } from "app/models"; +import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData, ModalModel, ButtonModel } from "app/models"; import { ResourceType } from "app/utils"; -import property = require("lodash/property"); import {ComponentServiceNg2} from "../../services/component-services/component.service"; import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service" import { InputBEModel, InputFEModel, ComponentInstance, PropertyBEModel, DerivedFEProperty, ResourceInstance, SimpleFlatProperty } from "app/models"; @@ -35,6 +35,9 @@ import {PropertyRowSelectedEvent} from "../../components/logic/properties-table/ import {HierarchyNavService} from "./services/hierarchy-nav.service"; import {PropertiesUtils} from "./services/properties.utils"; import {ComponentModeService} from "../../services/component-services/component-mode.service"; +import {ModalService} from "../../services/modal.service"; +import {Tabs, Tab} from "../../components/ui/tabs/tabs.component"; +import {InputsUtils} from "./services/inputs.utils"; @Component({ templateUrl: './properties-assignment.page.component.html', @@ -64,24 +67,36 @@ export class PropertiesAssignmentComponent { hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name'); displayClearSearch = false; searchPropertyName:string; - isInpusTabSelected:boolean; + currentMainTab:Tab; + isInputsTabSelected:boolean; + isPropertiesTabSelected:boolean; isReadonly:boolean; loadingInstances:boolean = false; loadingInputs:boolean = false; loadingProperties:boolean = false; - - @ViewChild('hierarchyNavTabs') hierarchyNavTabs: ElementRef; - @ViewChild('propertyInputTabs') propertyInputTabs: ElementRef; + changedData:Array<PropertyFEModel|InputFEModel>; + hasChangedData:boolean; + isValidChangedData:boolean; + savingChangedData:boolean; + stateChangeStartUnregister:Function; + + @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs; + @ViewChild('propertyInputTabs') propertyInputTabs: Tabs; @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent; + @ViewChild('saveChangedDataModalContentTemplate') saveChangedDataModalContentTemplateRef: TemplateRef<void>; constructor(private propertiesService: PropertiesService, private hierarchyNavService: HierarchyNavService, private propertiesUtils:PropertiesUtils, + private inputsUtils:InputsUtils, private componentServiceNg2:ComponentServiceNg2, private componentInstanceServiceNg2:ComponentInstanceServiceNg2, @Inject("$stateParams") _stateParams, - private renderer: Renderer, + @Inject("$scope") private $scope:ng.IScope, + @Inject("$state") private $state:ng.ui.IStateService, + @Inject("Notification") private Notification:any, private componentModeService:ComponentModeService, + private ModalService:ModalService, private EventListenerService:EventListenerService) { this.instanceFePropertiesMap = new InstanceFePropertiesMap(); @@ -91,6 +106,10 @@ export class PropertiesAssignmentComponent { this.component = _stateParams.component; this.EventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, this.onCheckout); this.updateViewMode(); + + this.changedData = []; + this.updateHasChangedData(); + this.isValidChangedData = true; } ngOnInit() { @@ -102,7 +121,9 @@ export class PropertiesAssignmentComponent { .getComponentInputs(this.component) .subscribe(response => { _.forEach(response.inputs, (input: InputBEModel) => { - this.inputs.push(new InputFEModel(input)); //only push items that were declared via SDC + const newInput: InputFEModel = new InputFEModel(input); + this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue); + this.inputs.push(newInput); //only push items that were declared via SDC }); this.loadingInputs = false; @@ -123,10 +144,22 @@ export class PropertiesAssignmentComponent { this.selectFirstInstanceByDefault(); }, error => {}); //ignore error + this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => { + // stop if has changed properties + if (this.hasChangedData) { + event.preventDefault(); + this.openChangedDataModal().then((proceed) => { + if (proceed) { + this.$state.go(toState, toParams); + } + }); + } + }); }; ngOnDestroy() { this.EventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT); + this.stateChangeStartUnregister(); } selectFirstInstanceByDefault = () => { @@ -147,12 +180,23 @@ export class PropertiesAssignmentComponent { onInstanceSelectedUpdate = (resourceInstance: ResourceInstance) => { console.log("==>" + this.constructor.name + ": onInstanceSelectedUpdate"); + + // stop if has changed properties + if (this.hasChangedData) { + this.openChangedDataModal().then((proceed) => { + if (proceed) { + this.onInstanceSelectedUpdate(resourceInstance); + } + }); + return; + } + let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); this.selectedInstanceData = resourceInstance; this.selectedInstanceType = resourceInstance.originType; this.loadingProperties = true; - if(this.isInput(resourceInstance.originType)) { + if (this.isInput(resourceInstance.originType)) { this.componentInstanceServiceNg2 .getComponentInstanceInputs(this.component, resourceInstance) .subscribe(response => { @@ -160,7 +204,8 @@ export class PropertiesAssignmentComponent { this.processInstancePropertiesResponse(instanceBePropertiesMap, true); this.loadingProperties = false; - }, error => {}); //ignore error + }, error => { + }); //ignore error } else { this.componentInstanceServiceNg2 .getComponentInstanceProperties(this.component, resourceInstance.uniqueId) @@ -168,14 +213,15 @@ export class PropertiesAssignmentComponent { instanceBePropertiesMap[resourceInstance.uniqueId] = response; this.processInstancePropertiesResponse(instanceBePropertiesMap, false); this.loadingProperties = false; - }, error => {}); //ignore error + }, error => { + }); //ignore error } - if(resourceInstance.componentName === "vnfConfiguration") { + if (resourceInstance.componentName === "vnfConfiguration") { this.isReadonly = true; } - if( this.searchPropertyName ){ + if (this.searchPropertyName) { this.clearSearch(); } //clear selected property from the navigation @@ -193,41 +239,31 @@ export class PropertiesAssignmentComponent { /*** VALUE CHANGE EVENTS ***/ - propertyValueChanged = (event: PropertyFEModel) => { - console.log("==>" + this.constructor.name + ": propertyValueChanged " + event); - // Copying the actual value from the object ref into the value if it's from a complex type - event.value = event.getJSONValue(); - - if (this.isInput(this.selectedInstanceData.originType)) { - console.log("I want to update input value on the resource instance"); - let inputToUpdate = new PropertyBEModel(event); - this.componentInstanceServiceNg2 - .updateInstanceInput(this.component, this.selectedInstanceData.uniqueId, inputToUpdate) - .subscribe(response => { - console.log("Update resource instance input response: ", response); - }, error => {}); //ignore error - } - else { - let propertyBe = new PropertyBEModel(event); - this.componentInstanceServiceNg2 - .updateInstanceProperty(this.component, this.selectedInstanceData.uniqueId, propertyBe) - .subscribe(response => { - console.log("Update resource instance property response: ", response); - }, error => {}); //ignore error - console.log(event); + dataChanged = (item:PropertyFEModel|InputFEModel) => { + let itemHasChanged; + if (this.isPropertiesTabSelected && item instanceof PropertyFEModel) { + itemHasChanged = item.hasValueObjChanged(); + } else if (this.isInputsTabSelected && item instanceof InputFEModel) { + itemHasChanged = item.hasDefaultValueChanged(); } - }; - - inputValueChanged = (event) => { - console.log("==>" + this.constructor.name + ": inputValueChanged"); - let inputToUpdate = new PropertyBEModel(event); + const dataChangedIdx = this.changedData.findIndex((changedItem) => changedItem === item); + if (itemHasChanged) { + if (dataChangedIdx === -1) { + this.changedData.push(item); + } + } else { + if (dataChangedIdx !== -1) { + this.changedData.splice(dataChangedIdx, 1); + } + } - this.componentServiceNg2 - .updateComponentInput(this.component, inputToUpdate) - .subscribe(response => { - console.log("updated the component input and got this response: ", response); - }, error => {}); //ignore error + if (this.isPropertiesTabSelected) { + this.isValidChangedData = this.changedData.every((changedItem) => (<PropertyFEModel>changedItem).valueObjIsValid); + } else if (this.isInputsTabSelected) { + this.isValidChangedData = this.changedData.every((changedItem) => (<InputFEModel>changedItem).defaultValueObjIsValid); + } + this.updateHasChangedData(); }; @@ -272,7 +308,7 @@ export class PropertiesAssignmentComponent { // Set selected property in table this.selectedFlatProperty = this.hierarchyNavService.createSimpleFlatProperty(property, instanceName); - this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Property Structure']); + this.hierarchyNavTabs.triggerTabChange('Property Structure'); }; @@ -280,13 +316,27 @@ export class PropertiesAssignmentComponent { this.selectedInstanceData = _.find(this.instancesNavigationData, (instance:ComponentInstance) => { return instance.name == $event; }); - this.renderer.invokeElementMethod( - this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + this.hierarchyNavTabs.triggerTabChange('Composition'); }; tabChanged = (event) => { + // stop if has changed properties + if (this.hasChangedData) { + this.openChangedDataModal().then((proceed) => { + if (proceed) { + this.propertyInputTabs.selectTab(this.propertyInputTabs.tabs.find((tab) => tab.title === event.title)); + } + }); + + // return to show the current tab + this.propertyInputTabs.triggerTabChange(this.currentMainTab.title); + return; + } + console.log("==>" + this.constructor.name + ": tabChanged " + event); - this.isInpusTabSelected = event.title === "Inputs"; + this.currentMainTab = this.propertyInputTabs.tabs.find((tab) => tab.title === event.title); + this.isPropertiesTabSelected = this.currentMainTab.title === "Properties"; + this.isInputsTabSelected = this.currentMainTab.title === "Inputs"; this.propertyStructureHeader = null; this.searchQuery = ''; }; @@ -320,12 +370,180 @@ export class PropertiesAssignmentComponent { this.checkedPropertiesCount = 0; _.forEach(response, (input: InputBEModel) => { let newInput: InputFEModel = new InputFEModel(input); + this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue); this.inputs.push(newInput); this.updatePropertyValueAfterDeclare(newInput); }); }, error => {}); //ignore error }; + saveChangedData = ():Promise<(PropertyBEModel|InputBEModel)[]> => { + return new Promise((resolve, reject) => { + if (!this.isValidChangedData) { + reject('Changed data is invalid - cannot save!'); + return; + } + if (!this.changedData.length) { + resolve([]); + return; + } + + // make request and its handlers + let request; + let handleSuccess, handleError; + if (this.isPropertiesTabSelected) { + const changedProperties: PropertyBEModel[] = this.changedData.map((changedProp) => { + changedProp = <PropertyFEModel>changedProp; + const propBE = new PropertyBEModel(changedProp); + propBE.value = changedProp.getJSONValue(); + return propBE; + }); + + if (this.isInput(this.selectedInstanceData.originType)) { + request = this.componentInstanceServiceNg2 + .updateInstanceInputs(this.component, this.selectedInstanceData.uniqueId, changedProperties); + handleSuccess = (response) => { + // reset each changed property with new value and remove it from changed properties list + response.forEach((resInput) => { + const changedProp = <PropertyFEModel>this.changedData.shift(); + this.propertiesUtils.resetPropertyValue(changedProp, resInput.value); + }); + console.log('updated instance inputs:', response); + }; + } else { + request = this.componentInstanceServiceNg2 + .updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties) + handleSuccess = (response) => { + // reset each changed property with new value and remove it from changed properties list + response.forEach((resProp) => { + const changedProp = <PropertyFEModel>this.changedData.shift(); + this.propertiesUtils.resetPropertyValue(changedProp, resProp.value); + }); + resolve(response); + console.log("updated instance properties: ", response); + }; + } + } else if (this.isInputsTabSelected) { + const changedInputs: InputBEModel[] = this.changedData.map((changedInput) => { + changedInput = <InputFEModel>changedInput; + const inputBE = new InputBEModel(changedInput); + inputBE.defaultValue = changedInput.getJSONDefaultValue(); + return inputBE; + }); + request = this.componentServiceNg2 + .updateComponentInputs(this.component, changedInputs); + handleSuccess = (response) => { + // reset each changed property with new value and remove it from changed properties list + response.forEach((resInput) => { + const changedInput = <InputFEModel>this.changedData.shift(); + this.inputsUtils.resetInputDefaultValue(changedInput, resInput.defaultValue); + }); + console.log("updated the component inputs and got this response: ", response); + } + } + + this.savingChangedData = true; + request.subscribe( + (response) => { + this.savingChangedData = false; + handleSuccess && handleSuccess(response); + this.updateHasChangedData(); + resolve(response); + }, + (error) => { + this.savingChangedData = false; + handleError && handleError(error); + this.updateHasChangedData(); + reject(error); + } + ); + }); + }; + + reverseChangedData = ():void => { + // make reverse item handler + let handleReverseItem; + if (this.isPropertiesTabSelected) { + handleReverseItem = (changedItem) => { + changedItem = <PropertyFEModel>changedItem; + this.propertiesUtils.resetPropertyValue(changedItem, changedItem.value); + }; + } else if (this.isInputsTabSelected) { + handleReverseItem = (changedItem) => { + changedItem = <InputFEModel>changedItem; + this.inputsUtils.resetInputDefaultValue(changedItem, changedItem.defaultValue); + }; + } + + this.changedData.forEach(handleReverseItem); + this.changedData = []; + this.updateHasChangedData(); + }; + + updateHasChangedData = ():boolean => { + const curHasChangedData:boolean = (this.changedData.length > 0); + if (curHasChangedData !== this.hasChangedData) { + this.hasChangedData = curHasChangedData; + this.$scope.$emit('setWorkspaceTopBarActive', !this.hasChangedData); + } + return this.hasChangedData; + }; + + doSaveChangedData = ():void => { + this.saveChangedData().then( + () => { + this.Notification.success({ + message: 'Successfully saved changes', + title: 'Saved' + }); + }, + () => { + this.Notification.error({ + message: 'Failed to save changes!', + title: 'Failure' + }); + } + ); + }; + + openChangedDataModal = ():Promise<boolean> => { + let modalTitle; + if (this.isPropertiesTabSelected) { + modalTitle = `Unsaved properties for ${this.selectedInstanceData.name}`; + } else if (this.isInputsTabSelected) { + modalTitle = `Unsaved inputs for ${this.component.name}`; + } + + return new Promise<boolean>((resolve) => { + const modal = this.ModalService.createCustomModal(new ModalModel( + 'sm', + modalTitle, + null, + [ + new ButtonModel('Cancel', 'outline grey', () => { + modal.instance.close(); + resolve(false); + }), + new ButtonModel('Discard', 'outline blue', () => { + this.reverseChangedData(); + modal.instance.close(); + resolve(true); + }), + new ButtonModel('Save', 'blue', () => { + this.saveChangedData().then(() => { + modal.instance.close(); + resolve(true); + }, () => { + modal.instance.close(); + resolve(false); + }); + }, () => !this.isValidChangedData) + ] + )); + this.ModalService.addDynamicTemplateToModal(modal, this.saveChangedDataModalContentTemplateRef); + modal.instance.open(); + }); + }; updatePropertyValueAfterDeclare = (input: InputFEModel) => { if (this.instanceFePropertiesMap[input.instanceUniqueId]) { @@ -346,12 +564,12 @@ export class PropertiesAssignmentComponent { }; setInputTabIndication = (numInputs: number): void => { - this.renderer.invokeElementMethod(this.propertyInputTabs, 'setTabIndication', ['Inputs', numInputs]); + this.propertyInputTabs.setTabIndication('Inputs', numInputs); }; deleteInput = (input: InputFEModel) => { console.log("==>" + this.constructor.name + ": deleteInput"); - let inputToDelete = new PropertyBEModel(input); + let inputToDelete = new InputBEModel(input); this.componentServiceNg2 .deleteInput(this.component, inputToDelete) @@ -389,7 +607,7 @@ export class PropertiesAssignmentComponent { this.processInstancePropertiesResponse(response, false); this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree this.searchPropertyName = filterData.propertyName;//mark in table - this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + this.hierarchyNavTabs.triggerTabChange('Composition'); this.propertiesNavigationData = []; this.displayClearSearch = true; }, error => {}); //ignore error @@ -408,12 +626,11 @@ export class PropertiesAssignmentComponent { clickOnClearSearch = () => { this.clearSearch(); this.selectFirstInstanceByDefault(); - this.renderer.invokeElementMethod( - this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + this.hierarchyNavTabs.triggerTabChange('Composition'); }; private isInput = (instanceType:string):boolean =>{ - return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC; + return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR; } } diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/services/hierarchy-nav.service.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/services/hierarchy-nav.service.ts index 016b04788e..1a800baac7 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/services/hierarchy-nav.service.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/services/hierarchy-nav.service.ts @@ -18,6 +18,7 @@ * ============LICENSE_END========================================================= */ +import * as _ from "lodash"; import { Injectable } from '@angular/core'; import { SimpleFlatProperty, PropertyFEModel, DerivedFEProperty } from 'app/models'; diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/services/inputs.utils.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/services/inputs.utils.ts new file mode 100644 index 0000000000..408a00e7b4 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/services/inputs.utils.ts @@ -0,0 +1,40 @@ +/*- + * ============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========================================================= + */ + +import { Injectable } from '@angular/core'; +import { InputFEModel} from "app/models"; + +@Injectable() +export class InputsUtils { + + constructor() {} + + public initDefaultValueObject = (input: InputFEModel): void => { + input.resetDefaultValueObjValidation(); + input.defaultValueObj = input.getDefaultValueObj(); + input.updateDefaultValueObjOrig(); + }; + + public resetInputDefaultValue = (input: InputFEModel, newDefaultValue: string): void => { + input.defaultValue = newDefaultValue; + this.initDefaultValueObject(input); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts index 8f46c6f603..e7b59b96ba 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts @@ -18,6 +18,7 @@ * ============LICENSE_END========================================================= */ +import * as _ from "lodash"; import { Injectable } from '@angular/core'; import { DataTypeModel, PropertyFEModel, PropertyBEModel, InstanceBePropertiesMap, InstanceFePropertiesMap, DerivedFEProperty, DerivedPropertyType, InputFEModel} from "app/models"; import { DataTypeService } from "app/ng2/services/data-type.service"; @@ -47,10 +48,13 @@ export class PropertiesUtils { let newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE - if (newFEProp.derivedDataType == DerivedPropertyType.COMPLEX) { //Create children if prop is not simple, list, or map. - newFEProp.flattenedChildren = this.createFlattenedChildren(newFEProp.type, newFEProp.name); - } - if (newFEProp.getInputValues && newFEProp.getInputValues.length) { //if this prop (or any children) are declared, set isDeclared and disable checkbox on parents/children + this.initValueObjectRef(newFEProp); //initialize valueObj. + propertyFeArray.push(newFEProp); + newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children + this.dataTypeService.checkForCustomBehavior(newFEProp); + + //if this prop (or any children) are declared, set isDeclared and disable checkbox on parents/children + if (newFEProp.getInputValues && newFEProp.getInputValues.length) { newFEProp.getInputValues.forEach(propInputDetail => { let inputPath = propInputDetail.inputPath; if (!inputPath) { //TODO: this is a workaround until Marina adds inputPath @@ -63,10 +67,6 @@ export class PropertiesUtils { this.propertiesService.disableRelatedProperties(newFEProp, inputPath); }); } - this.initValueObjectRef(newFEProp); //initialize valueObj. - propertyFeArray.push(newFEProp); - newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children - this.dataTypeService.checkForCustomBehavior(newFEProp); } }); instanceFePropertiesMap[instanceId] = propertyFeArray; @@ -103,33 +103,29 @@ export class PropertiesUtils { * Note: This logic is different than assignflattenedchildrenvalues - here we merge values, there we pick either the parents value, props value, or default value - without merging. */ public initValueObjectRef = (property: PropertyFEModel): void => { - if (property.derivedDataType == DerivedPropertyType.SIMPLE || property.isDeclared) { //if property is declared, it gets a simple input instead. List and map values and pseudo-children will be handled in property component - property.valueObj = property.value || property.defaultValue; - if (property.isDeclared) { - if(typeof property.valueObj == 'object'){ - property.valueObj = JSON.stringify(property.valueObj); - } - }else if(property.valueObj && - property.type !== PROPERTY_TYPES.STRING && - property.type !== PROPERTY_TYPES.JSON && - PROPERTY_DATA.SCALAR_TYPES.indexOf(property.type) == -1){ - property.valueObj = JSON.parse(property.valueObj);//The valueObj contains the real value ans not the value as string + property.resetValueObjValidation(); + if (property.isDeclared) { //if property is declared, it gets a simple input instead. List and map values and pseudo-children will be handled in property component + property.valueObj = property.value || property.defaultValue || null; // use null for empty value object + if (property.valueObj && typeof property.valueObj == 'object') { + property.valueObj = JSON.stringify(property.valueObj); } } else { - if (property.derivedDataType == DerivedPropertyType.LIST) { - property.valueObj = _.merge([], JSON.parse(property.defaultValue || '[]'), JSON.parse(property.value || '[]')); //value object should be merged value and default value. Value takes higher precendence. Set valueObj to empty obj if undefined. - } else { - property.valueObj = _.merge({}, JSON.parse(property.defaultValue || '{}'), JSON.parse(property.value || '{}')); //value object should be merged value and default value. Value takes higher precendence. Set valueObj to empty obj if undefined. - } - if ((property.derivedDataType == DerivedPropertyType.LIST || property.derivedDataType == DerivedPropertyType.MAP) && Object.keys(property.valueObj).length) { + property.valueObj = property.getValueObj(); + if (property.derivedDataType == DerivedPropertyType.LIST || property.derivedDataType == DerivedPropertyType.MAP) { + property.flattenedChildren = []; Object.keys(property.valueObj).forEach((key) => { property.flattenedChildren.push(...this.createListOrMapChildren(property, key, property.valueObj[key])) }); - } else { + } else if (property.derivedDataType === DerivedPropertyType.COMPLEX) { + property.flattenedChildren = this.createFlattenedChildren(property.type, property.name); this.assignFlattenedChildrenValues(property.valueObj, property.flattenedChildren, property.name); + property.flattenedChildren.forEach((childProp) => { + property.childPropUpdated(childProp); + }); } } - } + property.updateValueObjOrig(); + }; /* * Loops through flattened properties array and to assign values @@ -142,22 +138,24 @@ export class PropertiesUtils { derivedPropArray.forEach((prop, index) => { let propNameInObj = prop.propertiesName.substring(prop.propertiesName.indexOf(parentName) + parentName.length + 1).split('#').join('.'); //extract everything after parent name - prop.valueObj = _.get(parentValueJSON, propNameInObj, prop.value || prop.defaultValue); //assign value -first value of parent if exists. If not, prop.value if not, prop.defaultvalue + prop.valueObj = _.get(parentValueJSON, propNameInObj, prop.value || prop.defaultValue || null); //assign value -first value of parent if exists. If not, prop.value if not, prop.defaultvalue + prop.value = (prop.valueObj !== null && (typeof prop.valueObj) != 'string') ? JSON.stringify(prop.valueObj) : prop.valueObj; - if ( prop.isDeclared && typeof prop.valueObj == 'object') { //Stringify objects of items that are declared - prop.valueObj = JSON.stringify(prop.valueObj); - } else if(typeof prop.valueObj == PROPERTY_TYPES.STRING - && (prop.type == PROPERTY_TYPES.INTEGER || prop.type == PROPERTY_TYPES.FLOAT || prop.type == PROPERTY_TYPES.BOOLEAN)){ //parse ints and non-string simple types - prop.valueObj = JSON.parse(prop.valueObj); + if ((prop.isDeclared || prop.type == PROPERTY_TYPES.STRING || prop.type == PROPERTY_TYPES.JSON)) { //Stringify objects of items that are declared or from type string/json + prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == 'object') ? JSON.stringify(prop.valueObj) : prop.valueObj; + } else if(prop.type == PROPERTY_TYPES.INTEGER || prop.type == PROPERTY_TYPES.FLOAT || prop.type == PROPERTY_TYPES.BOOLEAN){ //parse ints and non-string simple types + prop.valueObj = (prop.valueObj !== null && typeof prop.valueObj == PROPERTY_TYPES.STRING) ? JSON.parse(prop.valueObj) : prop.valueObj; } else { //parse strings that should be objects - if (prop.derivedDataType == DerivedPropertyType.COMPLEX && typeof prop.valueObj != 'object') { - prop.valueObj = JSON.parse(prop.valueObj || '{}'); - } else if (prop.derivedDataType == DerivedPropertyType.LIST && typeof prop.valueObj != 'object') { - prop.valueObj = JSON.parse(prop.valueObj || '[]'); - } else if (prop.derivedDataType == DerivedPropertyType.MAP && typeof prop.valueObj != 'object' && (!prop.isChildOfListOrMap || !prop.schema.property.isSimpleType)) { //dont parse values for children of map of simple - prop.valueObj = JSON.parse(prop.valueObj || '{}'); + if (prop.derivedDataType == DerivedPropertyType.COMPLEX) { + prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj; + } else if (prop.derivedDataType == DerivedPropertyType.LIST) { + prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '[]') : prop.valueObj; + } else if (prop.derivedDataType == DerivedPropertyType.MAP) { + if (!prop.isChildOfListOrMap || !prop.schema.property.isSimpleType) { + prop.valueObj = (prop.valueObj === null || typeof prop.valueObj != 'object') ? JSON.parse(prop.valueObj || '{}') : prop.valueObj; + } } - if ((prop.derivedDataType == DerivedPropertyType.LIST || prop.derivedDataType == DerivedPropertyType.MAP) && typeof prop.valueObj == 'object' && Object.keys(prop.valueObj).length) { + if ((prop.derivedDataType == DerivedPropertyType.LIST || prop.derivedDataType == DerivedPropertyType.MAP) && typeof prop.valueObj == 'object' && prop.valueObj !== null && Object.keys(prop.valueObj).length) { let newProps: Array<DerivedFEProperty> = []; Object.keys(prop.valueObj).forEach((key) => { newProps.push(...this.createListOrMapChildren(prop, key, prop.valueObj[key]));//create new children, assign their values, and then add to array @@ -165,6 +163,8 @@ export class PropertiesUtils { propsToPushMap[index + 1] = newProps; } } + + prop.valueObj = PropertyFEModel.cleanValueObj(prop.valueObj); }); //add props after we're done looping (otherwise our loop gets messed up). Push in reverse order, so we dont mess up indexes. @@ -178,11 +178,10 @@ export class PropertiesUtils { if (nestedPath) { let newProp = property.flattenedChildren.find(prop => prop.propertiesName == nestedPath); newProp && this.assignFlattenedChildrenValues(JSON.parse(newValue), [newProp], property.name); + property.updateValueObjOrig(); } else { this.initValueObjectRef(property); } } - - } diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.html b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.html new file mode 100644 index 0000000000..bbbf6ae694 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.html @@ -0,0 +1,5 @@ +<ui-element-dropdown data-tests-id="linkSrc" [readonly]="!link.isFirst || (link.isFirst && !link.canEdit)" class="cell link-selector" [values]="source" [(value)]="link.fromNode" (valueChange)="onSourceSelected($event)"></ui-element-dropdown> +<ui-element-dropdown data-tests-id="linkSrcCP" [readonly]="!link.isFirst || (link.isFirst && !link.canEdit)" class="cell link-selector" [values]="srcCP" [(value)]="link.fromCP" (valueChange)="onSrcCPSelected($event)"></ui-element-dropdown> +<ui-element-dropdown data-tests-id="linkTarget" [readonly]="!link.canEdit" class="cell link-selector" [values]="target" [(value)]="link.toNode" (valueChange)="onTargetSelected($event)"></ui-element-dropdown> +<ui-element-dropdown data-tests-id="linkTargetCP" [readonly]="!link.canEdit" class="cell link-selector" [values]="targetCP" [(value)]="link.toCP" (valueChange)="onTargetCPSelected($event)"></ui-element-dropdown> +<div class="cell remove" data-tests-id="removeLnk"><span *ngIf="link.canRemove" class="sprite-new delete-item-icon" (click)="removeRow()"></span></div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.less b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.less new file mode 100644 index 0000000000..beec9bd567 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.less @@ -0,0 +1,21 @@ +@import './../../../../../assets/styles/variables.less'; +.remove { + display: flex; + align-items: center; + justify-content: center; +} + +.cell { + padding: 0; +} + +/deep/ .link-selector { + select { + height: 30px; + border: none; + stroke: none; + } + +} + + diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.ts b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.ts new file mode 100644 index 0000000000..16433242d6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.ts @@ -0,0 +1,110 @@ +import {Component, Input} from '@angular/core'; +import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {Link} from './link.model'; +import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map"; + +@Component({ + selector: 'link-row', + templateUrl: './link-row.component.html', + styleUrls: ['./link-row.component.less'] +}) + + +export class LinkRowComponent { + @Input() data:Array<ServicePathMapItem>; + @Input() link:Link; + @Input() removeRow:Function; + source:Array<DropdownValue> = []; + target: Array<DropdownValue> = []; + srcCP: Array<DropdownValue> = []; + targetCP: Array<DropdownValue> = []; + + ngOnChanges() { + if (this.data) { + this.parseInitialData(this.data); + } + } + + parseInitialData(data: Array<ServicePathMapItem>) { + this.source = this.convertValuesToDropDownOptions(data); + if (this.link.fromNode) { + let srcCPOptions = this.findOptions(data, this.link.fromNode); + if (!srcCPOptions) { return; } + this.srcCP = this.convertValuesToDropDownOptions(srcCPOptions); + if (this.link.fromCP) { + let targetOptions = this.findOptions(srcCPOptions, this.link.fromCP); + if (!targetOptions) { return; } + this.target = this.convertValuesToDropDownOptions(targetOptions); + if (this.link.toNode) { + let targetCPOptions = this.findOptions(targetOptions, this.link.toNode); + if (!targetCPOptions) { return; } + this.targetCP = this.convertValuesToDropDownOptions(targetCPOptions); + } + } + } + } + + private findOptions(items: Array<ServicePathMapItem>, nodeOrCPId: string) { + let item = items.find((dataItem)=> nodeOrCPId === dataItem.id); + if (item && item.data && item.data.options) { + return item.data.options; + } + console.warn('no option was found to match selection of Node/CP with id:' + nodeOrCPId); + return null; + } + + private convertValuesToDropDownOptions(values: Array<ServicePathMapItem>) : Array<DropdownValue> { + let result = []; + for (let i = 0; i < values.length ; i++) { + result[result.length] = new DropdownValue(values[i].id, values[i].data.name); + } + return result; + } + + onSourceSelected(id) { + if (id) { + let srcCPOptions = this.findOptions(this.data, id); + this.srcCP = this.convertValuesToDropDownOptions(srcCPOptions); + this.link.fromCP = ''; + this.link.toNode = ''; + this.link.toCP = ''; + this.target = []; + this.targetCP = []; + } + } + + onSrcCPSelected (id) { + if (id) { + let srcCPData = this.data.find((dataItem)=> this.link.fromNode === dataItem.id).data; + let srcCPOptions = srcCPData.options; + let targetOptions = this.findOptions(srcCPOptions, id); + this.target = this.convertValuesToDropDownOptions(targetOptions); + this.link.fromCPOriginId = srcCPData.ownerId; + this.link.toNode = ''; + this.link.toCP = ''; + this.targetCP = []; + } + + } + + onTargetSelected(id) { + if (id) { + let srcCPOptions = this.findOptions(this.data, this.link.fromNode); + let targetOptions = this.findOptions(srcCPOptions, this.link.fromCP); + let targetCPOptions = this.findOptions(targetOptions, id); + this.targetCP = this.convertValuesToDropDownOptions(targetCPOptions); + this.link.toCP = ''; + } + + } + + onTargetCPSelected(id) { + if (id) { + let srcCPOptions = this.findOptions(this.data, this.link.fromNode); + let targetOptions = this.findOptions(srcCPOptions, this.link.fromCP); + let targetCPOptions = this.findOptions(targetOptions, this.link.toNode); + let targetCPDataObj = targetCPOptions.find((dataItem)=> id === dataItem.id).data; + this.link.toCPOriginId = targetCPDataObj.ownerId; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link.model.ts b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link.model.ts new file mode 100644 index 0000000000..80128eb42e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link.model.ts @@ -0,0 +1,36 @@ +/*- + * ============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========================================================= + */ +'use strict'; +import {ForwardingPathLink} from "app/models/forwarding-path-link"; + +export class Link extends ForwardingPathLink { + public canEdit:boolean = false; + public canRemove:boolean = false; + public isFirst:boolean = false; + + constructor(link: ForwardingPathLink, canEdit: boolean, canRemove: boolean, isFirst: boolean) { + super(link.fromNode,link.fromCP, link.toNode, link.toCP, link.fromCPOriginId, link.toCPOriginId); + this.canEdit = canEdit; + this.canRemove = canRemove; + this.isFirst = isFirst; + } +} + + diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.html b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.html new file mode 100644 index 0000000000..96cd83eef6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.html @@ -0,0 +1,43 @@ +<div class="service-path-creator"> + <form class="w-sdc-form"> + <div class="i-sdc-form-item" > + <label class="i-sdc-form-label required">Path Name</label> + <!-- <ui-element-input type="text" name="pathName" [value]="pathName" ></ui-element-input> --> + <input type="text" data-tests-id="pathName" name="pathName" [(ngModel)]="forwardingPath.name" [attr.maxLength]="100" /> <!-- TODO - make unique --> + </div> + + <div class="side-by-side"> + <div class="i-sdc-form-item" > + <label class="i-sdc-form-label">Protocol</label> + <!-- <ui-element-input type="text" name="protocol" [value]="protocol" ></ui-element-input> --> + <input type="text" data-tests-id="pathProtocol" name="protocol" [(ngModel)]="forwardingPath.protocol" [attr.maxLength]="100" /> + </div> + <div class="i-sdc-form-item" > + <label class="i-sdc-form-label">Destination Port Numbers</label> + <!-- <ui-element-input type="text" name="portNumbers" [value]="portNumbers" ></ui-element-input> --> + <input type="text" data-tests-id="pathPortNumbers" name="portNumbers" [(ngModel)]="forwardingPath.destinationPortNumber" pattern="[0-9,]*" /> <!-- TODO - validate delimiter --> + </div> + </div> + + <div class="separator-buttons"> + <span class="based-on-title">Based On</span> + <a (click)="addRow()" [ngClass]="{'disabled':!isExtendAllowed()}" data-tests-id="extendPathlnk">Extend Path</a> + </div> + + <div class="generic-table"> + <div class="header-row"> + <div class="cell header-cell" *ngFor="let header of headers"> + {{header}} + </div> + </div> + <div *ngIf="links && links.length === 0" class="no-row-text" > + There is no data to display + </div> + <div> + <link-row *ngFor="let link of links" [data]="linksMap" [link]="link" [removeRow]="removeRow" class="data-row" ></link-row> + </div> + </div> + + + </form> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.less b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.less new file mode 100644 index 0000000000..5c9e53e229 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.less @@ -0,0 +1,45 @@ +@import './../../../../assets/styles/variables.less'; +.service-path-creator { + font-family: @font-opensans-regular; + .separator-buttons { + margin: 10px 0; + display: flex; + justify-content: space-between; + } + .i-sdc-form-label { + font-size: 12px; + } + .w-sdc-form .i-sdc-form-item { + margin-bottom: 15px; + } + + .side-by-side { + display: flex; + .i-sdc-form-item { + flex-basis: 100%; + &:first-child { + margin-right: 10px; + } + } + } + + .generic-table { + max-height: 233px; + .header-row .header-cell { + &:last-child { + padding: 0; + } + } + /deep/ .cell { + &:last-child { + min-width: 30px; + } + } + } + + .based-on-title { + text-transform: uppercase; + font-size: 18px; + font-family: @font-opensans-regular; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.ts b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.ts new file mode 100644 index 0000000000..dac41a37bc --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.ts @@ -0,0 +1,137 @@ +/*- + * ============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========================================================= + */ + +import * as _ from "lodash"; +import { Component, ElementRef, forwardRef, Inject } from '@angular/core'; +import {Link} from './link-row/link.model'; +import {ForwardingPath} from 'app/models/forwarding-path'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {ForwardingPathLink} from "app/models/forwarding-path-link"; +import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map"; + +@Component({ + selector: 'service-path-creator', + templateUrl: './service-path-creator.component.html', + styleUrls:['./service-path-creator.component.less'], + providers: [ServiceServiceNg2] +}) + +export class ServicePathCreatorComponent { + + linksMap:Array<ServicePathMapItem>; + links:Array<Link> = []; + input:any; + headers: Array<string> = []; + removeRow: Function; + forwardingPath:ForwardingPath; + //isExtendAllowed:boolean = false; + + constructor(private serviceService: ServiceServiceNg2) { + this.forwardingPath = new ForwardingPath(); + this.links = [new Link(new ForwardingPathLink('', '', '', '', '', ''), true, false, true)]; + this.headers = ['Source', 'Source Connection Point', 'Target', 'Target Connection Point', ' ']; + this.removeRow = () => { + if (this.links.length === 1) { + return; + } + this.links.splice(this.links.length-1, 1); + this.enableCurrentRow(); + }; + } + + ngOnInit() { + this.serviceService.getNodesAndLinksMap(this.input.service).subscribe((res:any) => { + this.linksMap = res; + }); + this.processExistingPath(); + + } + + private processExistingPath() { + if (this.input.pathId) { + let forwardingPath = <ForwardingPath>{...this.input.service.forwardingPaths[this.input.pathId]}; + this.forwardingPath.name = forwardingPath.name; + this.forwardingPath.destinationPortNumber = forwardingPath.destinationPortNumber; + this.forwardingPath.protocol = forwardingPath.protocol; + this.forwardingPath.uniqueId = forwardingPath.uniqueId; + this.links = []; + _.forEach(forwardingPath.pathElements.listToscaDataDefinition, (link:ForwardingPathLink) => { + this.links[this.links.length] = new Link( link, false, false, false); + }); + this.links[this.links.length -1].canEdit = true; + this.links[this.links.length -1].canRemove = true; + this.links[0].isFirst = true; + + } + } + + isExtendAllowed():boolean { + if (this.links[this.links.length-1].toCP) { + return true; + } + return false; + } + + enableCurrentRow() { + this.links[this.links.length-1].canEdit = true; + if (this.links.length !== 1) { + this.links[this.links.length-1].canRemove = true; + } + } + + addRow() { + this.disableRows(); + this.links[this.links.length] = new Link( new ForwardingPathLink(this.links[this.links.length-1].toNode,this.links[this.links.length-1].toCP,'','',this.links[this.links.length-1].toCPOriginId,''),true, true, false); + } + + disableRows() { + for (let i = 0 ; i < this.links.length ; i++) { + this.links[i].canEdit = false; + this.links[i].canRemove = false; + } + } + + createPathLinksObject() { + for (let i = 0 ; i < this.links.length ; i++) { + let link = this.links[i]; + this.forwardingPath.addPathLink(link.fromNode, link.fromCP, link.toNode, link.toCP, link.fromCPOriginId, link.toCPOriginId); + } + } + + createServicePathData() { + this.createPathLinksObject(); + return this.forwardingPath; + } + + checkFormValidForSubmit():boolean { + if (this.forwardingPath.name && this.isPathValid() ) { + return true; + } + return false; + } + + isPathValid():boolean { + let lastLink = this.links[this.links.length -1] ; + if (lastLink.toNode && lastLink.toCP && lastLink.fromNode && lastLink.fromCP) { + return true; + } + return false; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.module.ts b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.module.ts new file mode 100644 index 0000000000..78005317a2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {ServicePathCreatorComponent} from "./service-path-creator.component"; +import {FormsModule} from "@angular/forms"; +import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; +import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; +import {LinkRowComponent} from './link-row/link-row.component' +@NgModule({ + declarations: [ + ServicePathCreatorComponent, + LinkRowComponent + ], + imports: [CommonModule, + FormsModule, + FormElementsModule, + UiElementsModule + ], + exports: [], + entryComponents: [ + ServicePathCreatorComponent + ], + providers: [] +}) +export class ServicePathCreatorModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.html b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.html new file mode 100644 index 0000000000..8a31c76998 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.html @@ -0,0 +1,21 @@ +<div class="service-path-list"> + <div class="add-path-link"><a (click)="onAddServicePath()" data-tests-id="add-service-path-lnk" >+ Add Path</a></div> + <div class="generic-table table-container" > + <div class="header-row"> + <div class="cell header-cell" *ngFor="let header of headers"> + {{header}} + </div> + </div> + <div *ngFor="let path of paths" class="data-row" > + <div class="cell" data-tests-id="path-name" >{{path.name}}</div> + <div class="cell path-action-buttons"> + <span class="sprite-new update-component-icon" (click)="onEditServicePath(path.uniqueId)" data-tests-id="update-service-path-btn" ></span> + <span class="sprite-new delete-item-icon" (click)="deletePath(path.uniqueId)" data-tests-id="delete-service-path-btn"></span> + </div> + </div> + <div *ngIf="paths && paths.length === 0" class="no-row-text" > + No paths have been added yet. + </div> + </div> + +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.less b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.less new file mode 100644 index 0000000000..aff597fd85 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.less @@ -0,0 +1,21 @@ +@import './../../../../assets/styles/variables.less'; + +.add-path-link { + display: flex; + align-items: flex-end; + flex-direction: column; + padding-bottom: 10px; +} + +.generic-table { + max-height: 233px; +} + +.path-action-buttons { + display: flex; + align-items: center; + justify-content: space-between; + .sprite-new { + cursor: pointer; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.ts b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.ts new file mode 100644 index 0000000000..04083e8685 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.ts @@ -0,0 +1,66 @@ +/*- + * ============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========================================================= + */ + +import * as _ from "lodash"; +import {Component, ComponentRef} from '@angular/core'; +import {ForwardingPath} from "app/models/forwarding-path"; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {ModalService} from "app/ng2/services/modal.service"; +import {ModalComponent} from "app/ng2/components/ui/modal/modal.component"; + +@Component({ + selector: 'service-paths-list', + templateUrl: './service-paths-list.component.html', + styleUrls:['service-paths-list.component.less'], + providers: [ServiceServiceNg2, ModalService] +}) +export default class ServicePathsListComponent { + modalInstance: ComponentRef<ModalComponent>; + headers: Array<string> = []; + paths: Array<ForwardingPath> = []; + input:any; + onAddServicePath: Function; + onEditServicePath: Function; + + constructor(private serviceService:ServiceServiceNg2) { + this.headers = ['Path Name','Actions']; + } + + ngOnInit() { + _.forEach(this.input.service.forwardingPaths, (path: ForwardingPath)=> { + this.paths[this.paths.length] = path; + }); + this.paths.sort((a:ForwardingPath, b:ForwardingPath)=> { + return a.name.localeCompare(b.name); + }); + this.onAddServicePath = this.input.onCreateServicePath; + this.onEditServicePath = this.input.onEditServicePath; + } + + deletePath = (id:string):void => { + this.serviceService.deleteServicePath(this.input.service, id).subscribe((res:any) => { + delete this.input.service.forwardingPaths[id]; + this.paths = this.paths.filter(function(path){ + return path.uniqueId !== id; + }); + }); + }; + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.module.ts b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.module.ts new file mode 100644 index 0000000000..c236934002 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import {CommonModule} from "@angular/common"; +import ServicePathsListComponent from "./service-paths-list.component"; + +@NgModule({ + declarations: [ + ServicePathsListComponent + ], + imports: [CommonModule], + exports: [], + entryComponents: [ + ServicePathsListComponent + ], + providers: [] +}) +export class ServicePathsListModule { +}
\ No newline at end of file |