Support occurrences on node templates 09/127209/6
authorJvD_Ericsson <jeff.van.dam@est.tech>
Thu, 17 Feb 2022 15:48:20 +0000 (15:48 +0000)
committerMichael Morris <michael.morris@est.tech>
Thu, 10 Mar 2022 13:27:17 +0000 (13:27 +0000)
Issue-ID: SDC-3711
Change-Id: I9f25454faa8be6987f336b7efd3821cfa09585a1
Signed-off-by: JvD_Ericsson <jeff.van.dam@est.tech>
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java
catalog-be/src/main/java/org/openecomp/sdc/be/tosca/ToscaExportHandler.java
catalog-be/src/main/java/org/openecomp/sdc/be/tosca/model/ToscaNodeTemplate.java
catalog-ui/src/app/models/componentsInstances/componentInstance.ts
catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.html
catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.less
catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.ts
common-be/src/main/java/org/openecomp/sdc/be/datatypes/elements/ComponentInstanceDataDefinition.java
common-be/src/main/java/org/openecomp/sdc/be/datatypes/enums/JsonPresentationFields.java

index ca4002f..2f712e1 100644 (file)
@@ -1263,6 +1263,8 @@ public class ComponentInstanceBusinessLogic extends BaseBusinessLogic {
         oldComponentInstance.setModificationTime(System.currentTimeMillis());
         oldComponentInstance.setCustomizationUUID(UUID.randomUUID().toString());
         oldComponentInstance.setDirectives(newComponentInstance.getDirectives());
+        oldComponentInstance.setMaxOccurrences(newComponentInstance.getMaxOccurrences());
+        oldComponentInstance.setMinOccurrences(newComponentInstance.getMinOccurrences());
         if (oldComponentInstance.getGroupInstances() != null) {
             oldComponentInstance.getGroupInstances().forEach(group -> group.setName(getNewGroupName(oldComponentInstance.getNormalizedName(),
                 ValidationUtils.normalizeComponentInstanceName(newComponentInstance.getName()), group.getName())));
index ef63a86..5c30d09 100644 (file)
@@ -46,6 +46,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import com.google.common.primitives.Ints;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.io.FilenameUtils;
@@ -912,6 +913,12 @@ public class ToscaExportHandler {
             if (MapUtils.isNotEmpty(componentInstance.getToscaArtifacts())) {
                 nodeTemplate.setArtifacts(convertToNodeTemplateArtifacts(componentInstance.getToscaArtifacts()));
             }
+            if (componentInstance.getMinOccurrences() != null && componentInstance.getMaxOccurrences()!= null){
+                List<Object> occur = new ArrayList<Object>();
+                occur.add(parseToIntIfPossible(componentInstance.getMinOccurrences()));
+                occur.add(parseToIntIfPossible(componentInstance.getMaxOccurrences()));
+                nodeTemplate.setOccurrences(occur);
+            }
             nodeTemplate.setType(componentInstance.getToscaComponentName());
             nodeTemplate.setDirectives(componentInstance.getDirectives());
             nodeTemplate.setNode_filter(convertToNodeTemplateNodeFilterComponent(componentInstance.getNodeFilter()));
@@ -1015,7 +1022,12 @@ public class ToscaExportHandler {
         log.debug("finish convert topology template for {} for type {}", component.getUniqueId(), component.getComponentType());
         return convertNodeTemplatesRes;
     }
-
+  
+    private Object parseToIntIfPossible(final String value) {
+        final Integer intValue = Ints.tryParse(value);
+        return intValue == null ? value : intValue;
+    }
+  
     private void handleInstanceInterfaces(
         Map<String, List<ComponentInstanceInterface>> componentInstanceInterfaces,
         ComponentInstance componentInstance, Map<String, DataTypeDefinition> dataTypes, ToscaNodeTemplate nodeTemplate,
index f909a9c..0a75870 100644 (file)
@@ -34,6 +34,7 @@ import org.apache.commons.collections.MapUtils;
 public class ToscaNodeTemplate {
 
     private String type;
+    private List<Object> occurrences;
     private List<String> directives;
     private Map<String, String> metadata;
     private String description;
index c3188ba..86411b2 100644 (file)
@@ -94,6 +94,8 @@ export class ComponentInstance implements IComponentInstance{
     public sourceModelName:string;
     public sourceModelUid:string;
     public sourceModelUuid:string;
+    public minOccurrences: string;
+    public maxOccurrences: string;
     //custom properties
     public certified:boolean;
     public iconSprite:string;
@@ -137,6 +139,8 @@ export class ComponentInstance implements IComponentInstance{
             this.originArchived = componentInstance.originArchived;
             this.directives = componentInstance.directives;
             this.interfaces = componentInstance.interfaces;
+            this.minOccurrences = componentInstance.minOccurrences;
+            this.maxOccurrences = componentInstance.maxOccurrences;
         }
     }
 
index 86c6fea..358c910 100644 (file)
             </sdc-accordion>
         </ng-container>
         </div>
-    </div>            
+
+    </div>
+    </content>
+</ng2-expand-collapse>
+
+<ng2-expand-collapse state="0">
+    <header sdc-tooltip tooltip-text="Occurrences">OCCURRENCES</header>
+    <content>
+    <div class="w-sdc-designer-sidebar-section">
+       <div *ngIf="isUnboundedChecked != null">
+        <ng-container>
+            <sdc-accordion [title]="component.name + ' Occurrences'" [arrow-direction]="'right'" [testId]="'Occurrences'" [css-class]="'occurrences-accordion'">
+                <div class="i-sdc-designer-sidebar-section-content-item">
+                    <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute">
+                        <checkbox [(checked)]="isOccurrencesEnabled" (change)="enableOccurrences()" [disabled]="isViewOnly"></checkbox>
+                        <div>
+                        <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label"
+                              [ngClass]="{'hand enabled': !isViewOnly}">Enable Occurrences</span>
+                        </div>
+                    </div>
+                    <div *ngIf="isOccurrencesEnabled" class="i-sdc-designer-sidebar-section-content-item-property-and-attribute">
+                        <div class="sdc-input">
+                            <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label">Min Occurrences</span>
+                            <div class="min-occurrences-value">
+                                <sdc-input
+                                    label=""
+                                    testId="reqOccurrencesMin"
+                                    [disabled]="isViewOnly"
+                                    [(value)]="component.minOccurrences"
+                                    type="number">
+                                </sdc-input>
+                            </div>
+                        </div>
+                    </div>
+                    <div *ngIf="isOccurrencesEnabled" class="i-sdc-designer-sidebar-section-content-item-property-and-attribute">
+                        <div class="sdc-input">
+                            <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label">Max Occurrences</span>
+                            <div class="max-occurrences-value">
+                                <sdc-checkbox
+                                        class="checkbox-label unbounded-value"
+                                        testId="reqOccurrencesMaxUnbounded"
+                                        label="Unbounded"
+                                        (checkedChange)="onUnboundedChanged(component)"
+                                        [checked]="isUnboundedChecked"
+                                        [disabled]="isViewOnly">
+                                </sdc-checkbox>
+                                <sdc-input
+                                        *ngIf="!isUnboundedChecked"
+                                        testId="reqOccurrencesMax"
+                                        [disabled]="isViewOnly"
+                                        [(value)]="component.maxOccurrences"
+                                        type="number">
+                                </sdc-input>
+                            </div>
+                        </div>
+                    </div>
+                    <div *ngIf="!isViewOnly && isOccurrencesEnabled" class="i-sdc-designer-sidebar-section-content-item-property-and-attribute">
+                        <button class="tlv-btn blue" (click)="saveOccurrences()" [disabled]="!isOccurrencesFormValid(component)">Save</button>
+                    </div>
+                </div>
+                </sdc-accordion>
+        </ng-container>
+        </div>
+    </div>
     </content>
 </ng2-expand-collapse>
index 5cb0697..ac0d170 100644 (file)
         flex: 0 0 auto;
         align-self: center;
     }
+
+    /deep/ .checkbox-container {
+        margin-right: 10px;
+    }
 }
 
 .i-sdc-designer-sidebar-section-content-item-property-and-attribute-label {
     display: block;
 }
 
-
-
 /deep/ .expand-collapse-content {
     max-height: max-content;
     padding: 10px 0;
 
     .sdc-accordion .sdc-accordion-header {
-        
+
             background-color: #e6f6fb;
             border-left: solid #009fdb 4px;
             box-shadow: 0 0px 3px -1px rgba(0, 0, 0, 0.3);
index b4b8248..99bd5e8 100644 (file)
@@ -4,6 +4,7 @@ import {
     AttributeModel,
     AttributesGroup,
     Component as TopologyTemplate,
+    ComponentInstance,
     ComponentMetadata,
     FullComponentInstance,
     PropertiesGroup,
@@ -14,6 +15,7 @@ import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service';
 import { GroupByPipe } from 'app/ng2/pipes/groupBy.pipe';
 import { ResourceNamePipe } from 'app/ng2/pipes/resource-name.pipe';
 import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service';
+import { ComponentInstanceServiceNg2 } from "app/ng2/services/component-instance-services/component-instance.service";
 import { ComponentGenericResponse } from 'app/ng2/services/responses/component-generic-response';
 import { TranslateService } from 'app/ng2/shared/translator/translate.service';
 import { ModalsHandler } from 'app/utils';
@@ -33,6 +35,9 @@ export class PropertiesTabComponent implements OnInit {
     propertiesMessage: string;
     metadata: ComponentMetadata;
     objectKeys = Object.keys;
+    isUnboundedChecked: boolean;
+    isOccurrencesEnabled: boolean = false;
+    isLoading: boolean;
 
     @Input() isViewOnly: boolean;
     @Input() componentType: SelectedComponentType;
@@ -44,6 +49,7 @@ export class PropertiesTabComponent implements OnInit {
                 private compositionService: CompositionService,
                 private modalsHandler: ModalsHandler,
                 private topologyTemplateService: TopologyTemplateService,
+                private componentInstanceService: ComponentInstanceServiceNg2,
                 private modalService: SdcUiServices.ModalService,
                 private translateService: TranslateService,
                 private groupByPipe: GroupByPipe) {
@@ -176,6 +182,15 @@ export class PropertiesTabComponent implements OnInit {
         }
     }
 
+    private initComponentOccurrences = (): void => {
+        if (this.component instanceof FullComponentInstance) {
+            if(this.component.minOccurrences != null && this.component.maxOccurrences != null){
+                this.isOccurrencesEnabled = true;
+            }
+            this.isUnboundedChecked = this.component.maxOccurrences == "UNBOUNDED" ? true: false;
+        }
+    }
+
     /**
      * This function is checking if the component is the value owner of the current property
      * in order to notify the edit property modal which fields to disable
@@ -208,5 +223,58 @@ export class PropertiesTabComponent implements OnInit {
     private initPropertiesAndAttributes = (): void => {
         this.initComponentProperties();
         this.initComponentAttributes();
+        this.initComponentOccurrences();
+    }
+
+    onUnboundedChanged(component: ComponentInstance) {
+        this.isUnboundedChecked = !this.isUnboundedChecked;
+        component.maxOccurrences = this.isUnboundedChecked ? "UNBOUNDED" : "1";
+    }
+
+    private updateComponentInstance(component: ComponentInstance) {
+        this.store.dispatch(new TogglePanelLoadingAction({isLoading: true}));
+
+        this.componentInstanceService.updateComponentInstance(this.workspaceService.metadata.componentType,
+                                                              this.workspaceService.metadata.uniqueId, component)
+                                                              .subscribe((updatedComponentInstance: ComponentInstance) => {
+            component = new ComponentInstance(updatedComponentInstance);
+            this.compositionService.getComponentInstances().find((item) => item.uniqueId === component.uniqueId).maxOccurrences = component.maxOccurrences;
+            this.compositionService.getComponentInstances().find((item) => item.uniqueId === component.uniqueId).minOccurrences = component.minOccurrences;
+            this.store.dispatch(new TogglePanelLoadingAction({isLoading: false}));
+        }, (error:any) => {
+            this.store.dispatch(new TogglePanelLoadingAction({isLoading: false}));
+            if (error) {
+                console.log(error);
+            }});
+    }
+
+    private enableOccurrences = () => {
+        if(this.component instanceof FullComponentInstance){
+            if(!this.isOccurrencesEnabled){
+                this.component.minOccurrences = null;
+                this.component.maxOccurrences = null;
+            } else {
+                this.component.minOccurrences = "1";
+                this.component.maxOccurrences = "1";
+            }
+            this.updateComponentInstance(this.component);
+        }
+    }
+
+    private isOccurrencesFormValid(component: FullComponentInstance) {
+        if(
+            component.minOccurrences && parseInt(component.minOccurrences) >= 0 &&
+            component.maxOccurrences && (parseInt(component.maxOccurrences) >= parseInt(component.minOccurrences) || component.maxOccurrences === "UNBOUNDED")
+        ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private saveOccurrences = () => {
+        if(this.component instanceof FullComponentInstance && this.isOccurrencesFormValid(this.component)) {
+            this.updateComponentInstance(this.component);
+        }
     }
 }
index afcc0e6..ce8dda0 100644 (file)
@@ -53,6 +53,8 @@ public class ComponentInstanceDataDefinition extends ToscaDataDefinition {
         setDescription(dataDefinition.getDescription());
         setPosX(dataDefinition.getPosX());
         setPosY(dataDefinition.getPosY());
+        setMinOccurrences(dataDefinition.getMinOccurrences());
+        setMaxOccurrences(dataDefinition.getMaxOccurrences());
         setPropertyValueCounter(dataDefinition.getPropertyValueCounter());
         setNormalizedName(dataDefinition.getNormalizedName());
         setOriginType(dataDefinition.getOriginType());
@@ -138,6 +140,22 @@ public class ComponentInstanceDataDefinition extends ToscaDataDefinition {
         setToscaPresentationValue(JsonPresentationFields.CI_POS_Y, posY);
     }
 
+    public String getMinOccurrences() {
+        return (String) getToscaPresentationValue(JsonPresentationFields.CI_MIN_OCCURRENCES);
+    }
+
+    public void setMinOccurrences(String minOccurrences) {
+        setToscaPresentationValue(JsonPresentationFields.CI_MIN_OCCURRENCES, minOccurrences);
+    }
+
+    public String getMaxOccurrences() {
+        return (String) getToscaPresentationValue(JsonPresentationFields.CI_MAX_OCCURRENCES);
+    }
+
+    public void setMaxOccurrences(String maxOccurrences) {
+        setToscaPresentationValue(JsonPresentationFields.CI_MAX_OCCURRENCES, maxOccurrences);
+    }
+
     public String getComponentUid() {
         return (String) getToscaPresentationValue(JsonPresentationFields.CI_COMPONENT_UID);
     }
@@ -346,7 +364,8 @@ public class ComponentInstanceDataDefinition extends ToscaDataDefinition {
                 + getAttributeValueCounter() + ", inputValueCounter=" + getInputValueCounter() + ", originType="
                 + getOriginType() + ", customizationUUID=" + getCustomizationUUID() + ", componentName="
                 + getComponentName() + ", componentVersion=" + getComponentVersion() + ", toscaComponentName="
-                + getToscaComponentName() + ", directives =" + getDirectivesString() + "]";
+                + getToscaComponentName() + ", directives =" + getDirectivesString() + ", minOccurrences ="
+                + getMinOccurrences() + ", maxOccurrences =" + getMaxOccurrences() +"]";
     }
 
 }
index 8fce664..e36e4ef 100644 (file)
@@ -200,6 +200,8 @@ public enum JsonPresentationFields {
     CI_IS_PROXY("isProxy", null),
     CI_DIRECTIVES("directives", null),
     CI_ARTIFACTS("artifacts", null),
+    CI_MAX_OCCURRENCES("maxOccurrences", null),
+    CI_MIN_OCCURRENCES("minOccurrences", null),
 
     //path
     FORWARDING_PATH("forwardingPath", null),