Allow multiple entry for map/list when tosca function is selected
[sdc.git] / catalog-ui / src / app / ng2 / pages / properties-assignment / properties-assignment.page.component.ts
index 9f721d5..c828fab 100644 (file)
@@ -38,14 +38,16 @@ import {
     PropertyBEModel,
     PropertyFEModel,
     Service,
-    SimpleFlatProperty
+    SimpleFlatProperty,
+    PropertyDeclareAPIModel,
+    PropertiesGroup
 } from "app/models";
 import {ResourceType} from "app/utils";
 import {ComponentServiceNg2} from "../../services/component-services/component.service";
 import {TopologyTemplateService} from "../../services/component-services/topology-template.service";
 import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service"
 import {KeysPipe} from 'app/ng2/pipes/keys.pipe';
-import {EVENTS, PROPERTY_TYPES, WorkspaceMode} from "../../../utils/constants";
+import {EVENTS, PROPERTY_TYPES, WorkspaceMode, PROPERTY_DATA} from "../../../utils/constants";
 import {EventListenerService} from "app/services/event-listener-service"
 import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options";
 import {FilterPropertiesAssignmentComponent} from "../../components/logic/filter-properties-assignment/filter-properties-assignment.component";
@@ -61,13 +63,17 @@ import {UnsavedChangesComponent} from "app/ng2/components/ui/forms/unsaved-chang
 import {PropertyCreatorComponent} from "./property-creator/property-creator.component";
 import {ModalService} from "../../services/modal.service";
 import {DeclareListComponent} from "./declare-list/declare-list.component";
-import {ToscaFunctionComponent} from "./tosca-function/tosca-function.component";
+import {ToscaFunctionComponent, ToscaFunctionValidationEvent} from "./tosca-function/tosca-function.component";
 import {CapabilitiesGroup, Capability} from "../../../models/capability";
 import {ToscaPresentationData} from "../../../models/tosca-presentation";
 import {Observable} from "rxjs";
 import {TranslateService} from "../../shared/translator/translate.service";
-import {ToscaGetFunctionDtoBuilder} from '../../../models/tosca-get-function-dto';
-import {ToscaGetFunction} from "../../../models/tosca-get-function";
+import {ToscaFunction} from "../../../models/tosca-function";
+import {SubPropertyToscaFunction} from "../../../models/sub-property-tosca-function";
+import {DeclareInputComponent} from "./declare-input/declare-input.component";
+import {ElementService} from "../../services/element.service";
+import {CustomToscaFunction} from "../../../models/default-custom-functions";
+import {ToscaFunctionType} from "../../../models/tosca-function-type.enum";
 
 const SERVICE_SELF_TITLE = "SELF";
 @Component({
@@ -80,6 +86,7 @@ export class PropertiesAssignmentComponent {
     component: ComponentData;
     componentInstanceNamesMap: { [key: string]: InstanceFeDetails } = {}; //key is the instance uniqueId
     componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>(); //key is the instance uniqueId
+    customToscaFunctions: Array<CustomToscaFunction> = [];
 
     propertiesNavigationData = [];
     instancesNavigationData = [];
@@ -95,6 +102,8 @@ export class PropertiesAssignmentComponent {
     selectedInstanceData: ComponentInstance | GroupInstance | PolicyInstance = null;
     checkedPropertiesCount: number = 0;
     checkedChildPropertiesCount: number = 0;
+    enableToscaFunction: boolean = false;
+    checkedToscaCount: number = 0;
 
     hierarchyPropertiesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens');
     hierarchyInstancesDisplayOptions: HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'archived', null, 'iconClass');
@@ -118,6 +127,9 @@ export class PropertiesAssignmentComponent {
     serviceBePropertiesMap: InstanceBePropertiesMap;
     serviceBeCapabilitiesPropertiesMap: InstanceBePropertiesMap;
     selectedInstance_FlattenCapabilitiesList: Capability[];
+    componentInstancePropertyMap : PropertiesGroup;
+    defaultInputName: string;
+    doNotExtendBaseType: boolean;
 
     @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs;
     @ViewChild('propertyInputTabs') propertyInputTabs: Tabs;
@@ -140,7 +152,8 @@ export class PropertiesAssignmentComponent {
                 private modalService: ModalService,
                 private keysPipe: KeysPipe,
                 private topologyTemplateService: TopologyTemplateService,
-                private translateService: TranslateService) {
+                private translateService: TranslateService,
+                private service: ElementService) {
 
         this.instanceFePropertiesMap = new InstanceFePropertiesMap();
         /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time
@@ -167,10 +180,18 @@ export class PropertiesAssignmentComponent {
                 this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue);
                 this.inputs.push(newInput); //only push items that were declared via SDC
             });
+            this.componentInstancePropertyMap = response.componentInstancesProperties;
             this.loadingInputs = false;
 
         }, error => {
         }); //ignore error
+
+        this.topologyTemplateService.getDefaultCustomFunction().toPromise().then((data) => {
+            for (let customFunction of data) {
+                this.customToscaFunctions.push(new CustomToscaFunction(customFunction));
+            }
+        });
+
         this.componentServiceNg2
         .getComponentResourcePropertiesData(this.component)
         .subscribe(response => {
@@ -218,7 +239,19 @@ export class PropertiesAssignmentComponent {
         }, error => {
             this.loadingInstances = false;
         }); //ignore error
+        let modelName = this.component.model ? this.component.model : null;
+        const categoryName= this.component.categories[0].name;
+        if (this.component.categories[0].name != null && this.component.model != null) {
+            this.service.getCategoryBaseTypes(categoryName, modelName).subscribe((response: ListBaseTypesResponse) => {
+                this.doNotExtendBaseType = response.doNotExtendBaseType;
+                this.loadingProperties = false;
 
+            }, error => {
+                //ignore error
+            }, () => {
+                this.loadingProperties = false;
+            });
+        }
         this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => {
             // stop if has changed properties
             if (this.hasChangedData) {
@@ -233,7 +266,7 @@ export class PropertiesAssignmentComponent {
       this.loadDataTypesByComponentModel(this.component.model);
     }
 
-    ngOnDestroy() {
+    ngOnDestroy(){
         this.eventListenerService.unRegisterObserver(EVENTS.ON_LIFECYCLE_CHANGE);
         this.stateChangeStartUnregister();
     }
@@ -260,8 +293,26 @@ export class PropertiesAssignmentComponent {
     showAddProperties = (): boolean => {
         if (this.component.isService() && !(<Service>this.component).isSubstituteCandidate()) {
             return false;
+        } else if (this.hideAddProperties()) {
+            return false;
         }
         return this.isSelf();
+
+    }
+
+    hideAddProperties = (): boolean => {
+        if (this.component.isService() && this.doNotExtendBaseType && this.isSelf) {
+            return true;
+        }
+            return false;
+
+    }
+
+    disablePropertyValue = () : boolean => {
+        if (this.component.isService() && this.doNotExtendBaseType && this.isSelf()){
+            return true;
+        }
+        return false;
     }
 
     getServiceProperties() {
@@ -356,6 +407,8 @@ export class PropertiesAssignmentComponent {
         //clear selected property from the navigation
         this.selectedFlatProperty = new SimpleFlatProperty();
         this.propertiesNavigationData = [];
+        this.checkedToscaCount = 0;
+        this.enableToscaFunction = false;
     };
 
     /**
@@ -512,17 +565,18 @@ export class PropertiesAssignmentComponent {
      * Select Tosca function value from defined values
      */
     selectToscaFunctionAndValues = (): void => {
-        const selectedInstanceData: ComponentInstance = this.getSelectedComponentInstance();
+        const selectedInstanceData: ComponentInstance | GroupInstance | PolicyInstance = this.getSelectedInstance();
         if (!selectedInstanceData) {
             return;
         }
         this.openToscaGetFunctionModal();
     }
 
-    private getSelectedComponentInstance(): ComponentInstance {
+    private getSelectedInstance(): ComponentInstance | GroupInstance | PolicyInstance {
         const instancesIds = this.keysPipe.transform(this.instanceFePropertiesMap, []);
         const instanceId: string = instancesIds[0];
-        return <ComponentInstance> this.instances.find(instance => instance.uniqueId == instanceId && instance instanceof ComponentInstance);
+        return <ComponentInstance | GroupInstance | PolicyInstance> this.instances.find(instance => 
+            instance.uniqueId == instanceId && (instance instanceof ComponentInstance || instance instanceof GroupInstance || instance instanceof PolicyInstance));
     }
 
     private buildCheckedInstanceProperty(): PropertyBEModel {
@@ -539,70 +593,187 @@ export class PropertiesAssignmentComponent {
         const modalTitle = this.translateService.translate('TOSCA_FUNCTION_MODAL_TITLE');
         const modalButtons = [];
         let disableSaveButtonFlag = true;
+        const modal = this.modalService.createCustomModal(new ModalModel(
+            'sm',
+            modalTitle,
+            null,
+            modalButtons,
+            null /* type */
+        ));
         modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_SAVE'), 'blue',
             () => {
-                const toscaGetFunction: ToscaGetFunction = modal.instance.dynamicContent.instance.toscaGetFunction;
-                if (toscaGetFunction.functionType) {
-                    this.updateCheckedInstancePropertyGetFunctionValue(toscaGetFunction);
+                const toscaGetFunction: ToscaFunction = modal.instance.dynamicContent.instance.toscaFunctionForm.value;
+                if (toscaGetFunction) {
+                    this.updateCheckedInstancePropertyFunctionValue(toscaGetFunction);
                 } else {
                     this.clearCheckedInstancePropertyValue();
                 }
-                modal.instance.close();
+                this.modalService.closeCurrentModal();
             },
             (): boolean => { return disableSaveButtonFlag }
         ));
         const checkedInstanceProperty = this.buildCheckedInstanceProperty();
         modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_CANCEL'), 'outline grey', () => {
-            modal.instance.close();
+            this.modalService.closeCurrentModal();
         }));
-        const modal = this.modalService.createCustomModal(new ModalModel(
-            'sm',
-            modalTitle,
-            null,
-            modalButtons,
-            null /* type */
-        ));
+
 
         this.modalService.addDynamicContentToModalAndBindInputs(modal, ToscaFunctionComponent, {
             'property': checkedInstanceProperty,
-            'componentInstanceMap': this.componentInstanceMap
+            'componentInstanceMap': this.componentInstanceMap,
+            'customToscaFunctions': this.customToscaFunctions
         });
-        modal.instance.dynamicContent.instance.onValidityChange.subscribe(isValid => {
-            disableSaveButtonFlag = !isValid;
+        modal.instance.dynamicContent.instance.onValidityChange.subscribe((validationEvent: ToscaFunctionValidationEvent) => {
+            disableSaveButtonFlag = !validationEvent.isValid;
         });
         modal.instance.open();
     }
 
     private clearCheckedInstancePropertyValue() {
         const checkedInstanceProperty: PropertyBEModel = this.buildCheckedInstanceProperty();
+        const currentValue : any = checkedInstanceProperty.value;
         checkedInstanceProperty.getInputValues = null;
         checkedInstanceProperty.value = null;
-        checkedInstanceProperty.toscaGetFunction = null;
-        this.updateInstanceProperty(checkedInstanceProperty);
+        checkedInstanceProperty.toscaFunction = null;
+        if (checkedInstanceProperty instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel>checkedInstanceProperty).propertiesName){
+            const propertiesNameArray = (<PropertyDeclareAPIModel>checkedInstanceProperty).propertiesName;
+            const parts = propertiesNameArray.split("#");
+            let currentKey = (<DerivedFEProperty>checkedInstanceProperty.input).toscaPath;
+            if (propertiesNameArray.length > 1){
+                const index = checkedInstanceProperty.subPropertyToscaFunctions.findIndex(existingSubPropertyToscaFunction => this.areEqual(existingSubPropertyToscaFunction.subPropertyPath, currentKey.length > 0 ? currentKey : parts.slice(1)));
+                checkedInstanceProperty.subPropertyToscaFunctions.splice(index, 1);
+            }
+            if (this.enableToscaFunction) {
+                this.processSubtoscaFunction(checkedInstanceProperty,null);
+                return;
+            }
+        }
+        if (this.selectedInstanceData instanceof ComponentInstance) {
+            var toRemove = this.changedData.filter(obj => {
+                return obj.name == checkedInstanceProperty.name && obj.type == checkedInstanceProperty.type && obj.value == null;
+            });
+            const index: number = this.changedData.indexOf(toRemove[0]);
+            if (index !== -1) {
+                this.changedData.splice(index, 1);
+            }
+            this.updateInstanceProperty(checkedInstanceProperty);
+        } else if (this.selectedInstanceData instanceof GroupInstance) {
+            this.updateGroupInstanceProperty(checkedInstanceProperty);
+        } else if (this.selectedInstanceData instanceof PolicyInstance) {
+            this.updatePolicyInstanceProperty(checkedInstanceProperty);
+        }
     }
 
-    private updateCheckedInstancePropertyGetFunctionValue(toscaGetFunction: ToscaGetFunction) {
-        const toscaGetFunctionBuilder: ToscaGetFunctionDtoBuilder =
-            new ToscaGetFunctionDtoBuilder()
-                .withPropertyUniqueId(toscaGetFunction.propertyUniqueId)
-                .withFunctionType(toscaGetFunction.functionType)
-                .withPropertySource(toscaGetFunction.propertySource)
-                .withPropertyName(toscaGetFunction.propertyName)
-                .withSourceName(toscaGetFunction.sourceName)
-                .withSourceUniqueId(toscaGetFunction.sourceUniqueId)
-                .withPropertyPathFromSource(toscaGetFunction.propertyPathFromSource);
+    private processSubtoscaFunction(checkedProperty : PropertyDeclareAPIModel, toscaFunction: ToscaFunction) {
+        const instancesIds = this.keysPipe.transform(this.instanceFePropertiesMap, []);
+        const instanceId: string = instancesIds[0];
+        this.instanceFePropertiesMap[instanceId].forEach(prop => {
+            if (prop.flattenedChildren) {
+                prop.flattenedChildren.forEach((child) => {
+                    if (child.isSelected && !child.isDeclared && !child.isDisabled) {
+                        prop.subPropertyToscaFunctions = checkedProperty.subPropertyToscaFunctions;
+                        if (toscaFunction) {
+                            child.value = toscaFunction.buildValueString();
+                            child.valueObj = toscaFunction.buildValueObject();
+                            child.toscaFunction = toscaFunction;
+                            this.hasChangedData = true;
+                            if (this.changedData.length == 0) {
+                                this.changedData.push(prop);
+                            }
+                        } else {
+                            child.valueObj = null;
+                            child.toscaFunction = null;
+                        }
+                        child.isSelected = false;
+                        this.togggleToscaBtn(false);                                
+                    }
+                });
+            }
+        });
+    }
 
+    private updateCheckedInstancePropertyFunctionValue(toscaFunction: ToscaFunction) {
         const checkedProperty: PropertyBEModel = this.buildCheckedInstanceProperty();
-        checkedProperty.toscaGetFunction = toscaGetFunctionBuilder.build();
-        this.updateInstanceProperty(checkedProperty);
+        if (checkedProperty instanceof PropertyDeclareAPIModel && (<PropertyDeclareAPIModel>checkedProperty).propertiesName){
+            const propertiesName = (<PropertyDeclareAPIModel>checkedProperty).propertiesName;
+            const parts = propertiesName.split("#");
+            let currentKey = (<DerivedFEProperty>checkedProperty.input).toscaPath;
+            if (checkedProperty.subPropertyToscaFunctions == null){
+                checkedProperty.subPropertyToscaFunctions = [];
+            }
+            let subPropertyToscaFunction = checkedProperty.subPropertyToscaFunctions.find(existingSubPropertyToscaFunction => this.areEqual(existingSubPropertyToscaFunction.subPropertyPath, currentKey.length > 0 ? currentKey : parts.slice(1)));
+            if (!subPropertyToscaFunction){
+                 subPropertyToscaFunction = new SubPropertyToscaFunction();
+                 checkedProperty.subPropertyToscaFunctions.push(subPropertyToscaFunction);
+            }
+            subPropertyToscaFunction.toscaFunction = toscaFunction;
+            subPropertyToscaFunction.subPropertyPath = currentKey.length > 0 ? currentKey : parts.slice(1);
+            if (this.enableToscaFunction) {
+                this.processSubtoscaFunction(checkedProperty,toscaFunction);
+                return;
+            }
+        } else {
+            checkedProperty.subPropertyToscaFunctions = null;
+            checkedProperty.toscaFunction = toscaFunction;
+        }
+        if (this.selectedInstanceData instanceof ComponentInstance) {
+            this.updateInstanceProperty(checkedProperty);
+        } else if (this.selectedInstanceData instanceof GroupInstance) {
+            this.updateGroupInstanceProperty(checkedProperty);
+        } else if (this.selectedInstanceData instanceof PolicyInstance) {
+            this.updatePolicyInstanceProperty(checkedProperty);
+        }
+    }
+
+    private isComplexSchemaType(propertyType: string): boolean {
+        return PROPERTY_DATA.SIMPLE_TYPES.indexOf(propertyType) === -1;
+    }
+
+    private isListOrMap(propertyType: string): boolean {
+        return PROPERTY_TYPES.MAP === propertyType || PROPERTY_TYPES.LIST === propertyType;
+    }
+
+    private areEqual(array1: string[], array2: string[]): boolean {
+        return array1.length === array2.length && array1.every(function(value, index) { return value === array2[index]})
     }
 
     updateInstanceProperty(instanceProperty: PropertyBEModel) {
         this.loadingProperties = true;
+        this.enableToscaFunction = false;
+        this.checkedToscaCount = 0;
+
         this.componentInstanceServiceNg2.updateInstanceProperties(this.component.componentType, this.component.uniqueId,
             this.selectedInstanceData.uniqueId, [instanceProperty])
         .subscribe(() => {
-            this.changeSelectedInstance(this.getSelectedComponentInstance());
+            this.changeSelectedInstance(this.getSelectedInstance());
+        }, (error) => {
+            this.loadingProperties = false;
+            console.error(error);
+        }, () => {
+            this.loadingProperties = false;
+        });
+    }
+
+    updateGroupInstanceProperty(instanceProperty: PropertyBEModel) {
+        this.loadingProperties = true;
+        this.componentInstanceServiceNg2.updateComponentGroupInstanceProperties(this.component.componentType, this.component.uniqueId,
+            this.selectedInstanceData.uniqueId, [instanceProperty])
+        .subscribe(() => {
+            this.changeSelectedInstance(this.getSelectedInstance());
+        }, (error) => {
+            this.loadingProperties = false;
+            console.error(error);
+        }, () => {
+            this.loadingProperties = false;
+        });
+    }
+
+    updatePolicyInstanceProperty(instanceProperty: PropertyBEModel) {
+        this.loadingProperties = true;
+        this.componentInstanceServiceNg2.updateComponentPolicyInstanceProperties(this.component.componentType, this.component.uniqueId,
+            this.selectedInstanceData.uniqueId, [instanceProperty])
+        .subscribe(() => {
+            this.changeSelectedInstance(this.getSelectedInstance());
         }, (error) => {
             this.loadingProperties = false;
             console.error(error);
@@ -611,8 +782,16 @@ export class PropertiesAssignmentComponent {
         });
     }
 
+    declareInput = (): void => {
+        if (this.checkedPropertiesCount == 1) {
+            this.openAddInputNameAndDeclareInputModal();
+        } else if (this.checkedPropertiesCount > 1) {
+            this.declareInputFromProperties();
+        }
+    }
+
     /*** DECLARE PROPERTIES/INPUTS ***/
-    declareProperties = (): void => {
+    declareInputFromProperties = (inputName?: string): void => {
         console.debug("==>" + this.constructor.name + ": declareProperties");
 
         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
@@ -627,6 +806,9 @@ export class PropertiesAssignmentComponent {
                 if (!this.isInput(selectedInstanceData.originType)) {
                     // convert Property FE model -> Property BE model, extract only checked
                     selectedComponentInstancesProperties[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
+                    if (inputName) {
+                        selectedComponentInstancesProperties[instanceId][0].inputName = inputName;
+                    }
                 } else {
                     selectedComponentInstancesInputs[instanceId] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId]);
                 }
@@ -685,6 +867,63 @@ export class PropertiesAssignmentComponent {
             }, error => {}); //ignore error
     };
 
+    generateDefaultInputName = (): string => {
+        let defaultInputName: string;
+        let instancesIds = this.keysPipe.transform(this.instanceFePropertiesMap, []);
+        angular.forEach(instancesIds, (instanceId: string) => {
+            let selectedProperty: PropertyBEModel = new PropertyBEModel(this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceId])[0]);
+            let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId);
+            defaultInputName = this.generateInputName(selectedInstanceData.invariantName, selectedProperty.name);
+        });
+        return defaultInputName;
+    }
+
+    private generateInputName = (componentName: string, propertyName: string): string => {
+        let inputName: string;
+        if (propertyName) {
+            if (propertyName.includes("::")) {
+                propertyName = propertyName.replace(/::/g, "_");
+            }
+            if (componentName) {
+                inputName = componentName + "_" + propertyName;
+            } else {
+                inputName = propertyName;
+            }
+        }
+        return inputName;
+    }
+
+    private openAddInputNameAndDeclareInputModal = (): void => {
+        const modalTitle = this.translateService.translate('ADD_INPUT_NAME_TO_DECLARE');
+        const modalButtons = [];
+        const modal = this.modalService.createCustomModal(new ModalModel(
+            'sm',
+            modalTitle,
+            null,
+            modalButtons,
+            null /* type */
+        ));
+        modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_SAVE'), 'blue',
+            () => {
+                const inputName: string = modal.instance.dynamicContent.instance.inputNameForm.value;
+                if (inputName) {
+                    this.declareInputFromProperties(inputName);
+                } else {
+                    this.notification.warning({
+                        message: 'Failed to set input name',
+                        title: 'Warning'
+                    });
+                }
+                this.modalService.closeCurrentModal();
+            }
+        ));
+        modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_CANCEL'), 'outline grey', () => {
+            this.modalService.closeCurrentModal();
+        }));
+        this.modalService.addDynamicContentToModal(modal, DeclareInputComponent, {defaultInputName: this.generateDefaultInputName()});
+        modal.instance.open();
+    }
+
     declareListProperties = (): void => {
         // get selected properties
         let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
@@ -1084,6 +1323,8 @@ export class PropertiesAssignmentComponent {
     //used for declare button, to keep count of newly checked properties (and ignore declared properties)
     updateCheckedPropertyCount = (increment: boolean): void => {
         this.checkedPropertiesCount += (increment) ? 1 : -1;
+        this.checkedToscaCount = 0;
+        this.enableToscaFunction = false;
         console.debug("CheckedProperties count is now.... " + this.checkedPropertiesCount);
     };
 
@@ -1091,6 +1332,15 @@ export class PropertiesAssignmentComponent {
         this.checkedChildPropertiesCount += (increment) ? 1 : -1;
     };
 
+    togggleToscaBtn = (toscaFlag: boolean) : void => {
+        this.checkedToscaCount += toscaFlag ? 1 : -1;
+        if(this.checkedToscaCount == 1){
+            this.enableToscaFunction = true;
+        }else{
+            this.enableToscaFunction = false;
+        }
+    };
+
     setInputTabIndication = (numInputs: number): void => {
         this.propertyInputTabs.setTabIndication('Inputs', numInputs);
     };