code: 400,
message: "Cannot change this properties constraints as the resource is an instance.",
messageId: "SVC4015"
+ }
+
+ #---------SVC4016-----------------------------
+ INPUT_NAME_ALREADY_EXIST: {
+ code: 400,
+ message: "Input name already exist.",
+ messageId: "SVC4016"
}
\ No newline at end of file
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
try {
validateUserExists(userId);
component = getAndValidateComponentForCreate(userId, componentId, componentType, shouldLockComp);
+ StorageOperationStatus status = validateInputName(component, componentInstInputsMapUi);
+ if (status != StorageOperationStatus.OK) {
+ log.debug("Input name already exist");
+ throw new ByResponseFormatComponentException(componentsUtils.getResponseFormat(ActionStatus.INPUT_NAME_ALREADY_EXIST));
+ }
result = propertyDeclarationOrchestrator.declarePropertiesToInputs(component, componentInstInputsMapUi).left()
.bind(inputsToCreate -> prepareInputsForCreation(userId, componentId, inputsToCreate)).right()
.map(componentsUtils::getResponseFormat);
}
}
+ private StorageOperationStatus validateInputName(final Component component, final ComponentInstInputsMap componentInstInputsMap) {
+ AtomicReference<StorageOperationStatus> storageOperationStatus = new AtomicReference<>(StorageOperationStatus.OK);
+ Map<String, List<ComponentInstancePropInput>> inputDeclaredProperties = new HashMap<>();
+ if (MapUtils.isNotEmpty(componentInstInputsMap.getComponentInstanceProperties())) {
+ inputDeclaredProperties = componentInstInputsMap.getComponentInstanceProperties();
+ } else if (MapUtils.isNotEmpty(componentInstInputsMap.getServiceProperties())) {
+ inputDeclaredProperties = componentInstInputsMap.getServiceProperties();
+ }
+
+ if (MapUtils.isNotEmpty(inputDeclaredProperties) && CollectionUtils.isNotEmpty(component.getInputs())) {
+ inputDeclaredProperties.values()
+ .forEach(componentInstancePropInputs ->
+ componentInstancePropInputs
+ .forEach(componentInstancePropInput -> component.getInputs()
+ .forEach(existingInput -> {
+ if (existingInput.getName().equals(componentInstancePropInput.getInputName())) {
+ storageOperationStatus.set(StorageOperationStatus.INVALID_VALUE);
+ }
+ })
+ )
+ );
+ }
+ return storageOperationStatus.get();
+ }
+
/**
* Creates a list input with a data type which has properties specified.
*
generatedInputPrefix =
generatedInputPrefix == null || generatedInputPrefix.isEmpty() ? capName : generatedInputPrefix + UNDERSCORE + capName;
}
- String generatedInputName = generateInputName(generatedInputPrefix, propInput);
+ String generatedInputName = null;
+ if (StringUtils.isNotEmpty(propInput.getInputName())) {
+ generatedInputName = propInput.getInputName();
+ } else {
+ generatedInputName = generateInputName(generatedInputPrefix, propInput);
+ }
log.debug("createInput: propOwner.uniqueId={}, propInput.parentUniqueId={}", propertiesOwner.getUniqueId(), propInput.getParentUniqueId());
return createInputFromProperty(component.getUniqueId(), propertiesOwner, generatedInputName, propInput, prop);
}
if (component == null || CollectionUtils.isEmpty(component.getInputs())) {
return Collections.emptyMap();
}
- return component.getInputs().stream().filter(InputDefinition::isMappedToComponentProperty).map(PropertyDataDefinition::getName)
- .collect(Collectors.toMap(inputName -> inputName, inputName -> new String[]{inputName}, (inputName1, inputName2) -> inputName1));
+ Map<String, String[]> propertyMapping = new HashMap<>();
+ List<InputDefinition> propertyMappedInputList = component.getInputs().stream().filter(InputDefinition::isMappedToComponentProperty).collect(
+ Collectors.toList());
+
+ if (CollectionUtils.isNotEmpty(propertyMappedInputList)) {
+ propertyMappedInputList.forEach(inputDefinition -> {
+ if (StringUtils.isNotEmpty(inputDefinition.getPropertyId())) {
+ Optional<PropertyDefinition> property = component.getProperties().stream()
+ .filter(propertyDefinition -> propertyDefinition.getUniqueId().equals(inputDefinition.getPropertyId())).findFirst();
+ if (property.isPresent()) {
+ propertyMapping.put(property.get().getName(), new String[]{inputDefinition.getName()});
+ }
+ } else {
+ propertyMapping.put(inputDefinition.getName(), new String[]{inputDefinition.getName()});
+ }
+ });
+ }
+ return propertyMapping;
}
private Map<String, String[]> buildSubstitutionMappingAttributesMapping(final Component component) {
// cache
CONVERT_COMPONENT_ERROR, COMPONENT_NOT_FOUND,
// Inputs
- INPUT_IS_NOT_CHILD_OF_COMPONENT, CFVC_LOOP_DETECTED, INPUT_ALREADY_EXIST,
+ INPUT_IS_NOT_CHILD_OF_COMPONENT, CFVC_LOOP_DETECTED, INPUT_ALREADY_EXIST, INPUT_NAME_ALREADY_EXIST,
// Outputs
OUTPUT_IS_NOT_CHILD_OF_COMPONENT, OUTPUT_ALREADY_EXIST,
//Forwarding Path related
*/
package org.openecomp.sdc.be.model;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
public class ComponentInstancePropInput extends ComponentInstanceProperty {
private String propertiesName;
private PropertyDefinition input;
+ private String inputName;
public ComponentInstancePropInput() {
super();
super(p);
}
- public String getPropertiesName() {
- return propertiesName;
- }
-
- public void setPropertiesName(String propertiesName) {
- this.propertiesName = propertiesName;
- }
-
- public PropertyDefinition getInput() {
- return input;
- }
-
- public void setInput(PropertyDefinition input) {
- this.input = input;
- }
-
public String[] getParsedPropNames() {
String[] tokens = null;
if (propertiesName != null && !propertiesName.isEmpty()) {
canBeDeclared: boolean;
mapKey: string;
mapKeyError: string;
- mapInlist: boolean;
+ mapInlist: boolean
+ inputName: string;
parentMapKey: string;
constructor(property: PropertyBEModel, parentName?: string, createChildOfListOrMap?: boolean, key?:string, value?:any) {
this.parentName = parentName;
this.propertiesName = parentName + '#' + this.name;
- if (property.type == PROPERTY_TYPES.LIST) {
+ if (property.type == PROPERTY_TYPES.LIST) {
let parentKey : string = null;
if(property.value != null) {
const valueJson = JSON.parse(property.value);
// this.constraints = property ? property.constraints : null;
this.valueObjIsValid = true;
this.derivedDataType = this.getDerivedPropertyType();
+ this.inputName = property.inputName;
}
public getActualMapKey() {
parentPropertyType: string;
subPropertyInputPath: string;
inputPath: string;
+ inputName: string;
toscaPresentation: ToscaPresentationData;
metadata: Metadata;
propertyConstraints: any;
this.toscaPresentation = property.toscaPresentation;
this.getPolicyValues = property.getPolicyValues;
this.inputPath = property.inputPath;
+ this.inputName = property.inputName;
this.metadata = property.metadata;
if (property.toscaFunction) {
this.toscaFunction = property.toscaFunction;
export class PropertyDeclareAPIModel extends PropertyBEModel{
input: PropertyBEModel;
propertiesName: string;
+ inputName: string;
constructor(property: PropertyFEModel, childProperty?: DerivedFEProperty) {
if (childProperty) {
this.input = childProperty;
this.propertiesName = childProperty.propertiesName;
+ this.inputName = childProperty.inputName;
}
}
import {ConstraintsComponent} from '../ng2/pages/properties-assignment/constraints/constraints.component';
import {TypeWorkspaceComponent} from "../ng2/pages/type-workspace/type-workspace.component";
import {TypeWorkspaceGeneralComponent} from "../ng2/pages/type-workspace/type-workspace-general/type-workspace-general.component";
+import {DeclareInputComponent} from "../ng2/pages/properties-assignment/declare-input/declare-input.component";
let moduleName: string = 'Sdc.Directives';
let directiveModule: ng.IModule = angular.module(moduleName, []);
inputs: [],
outputs: []
}) as angular.IDirectiveFactory);
+
+directiveModule.directive('declareInput', downgradeComponent({
+ component: DeclareInputComponent,
+ inputs: ['property'],
+ outputs: []
+}) as angular.IDirectiveFactory);
import {ToscaArtifactService} from "./services/tosca-artifact.service";
import {InterfaceDefinitionModule} from "./pages/interface-definition/interface-definition.module";
import {TypeWorkspaceModule} from "./pages/type-workspace/type-workspace.module";
+import {DeclareInputModule} from "./pages/properties-assignment/declare-input/declare-input.module";
declare const __ENV__: string;
PropertiesAssignmentModule,
AttributesOutputsModule,
PropertyCreatorModule,
+ DeclareInputModule,
DeclareListModule,
ToscaFunctionModule,
ConstraintsModule,
--- /dev/null
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ Copyright (C) 2023 Nordix Foundation
+ ~ ================================================================================
+ ~ 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.
+ ~
+ ~ SPDX-License-Identifier: Apache-2.0
+ ~ ============LICENSE_END=========================================================
+ -->
+
+<div class="declare-input">
+ <input id="myText" type="text" (keyup)="setInputName($event)"/>
+</div>
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DeclareInputComponent } from './declare-input.component';
+
+describe('DeclareInputComponent', () => {
+ let component: DeclareInputComponent;
+ let fixture: ComponentFixture<DeclareInputComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DeclareInputComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DeclareInputComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+import {Component, Input, OnInit} from '@angular/core';
+import {FormControl, FormGroup} from "@angular/forms";
+
+@Component({
+ selector: 'declare-input',
+ templateUrl: './declare-input.component.html'
+})
+export class DeclareInputComponent implements OnInit {
+ inputNameForm: FormControl = new FormControl(undefined);
+ formGroup: FormGroup = new FormGroup({
+ 'inputName': this.inputNameForm,
+ });
+ inputName: string;
+ constructor() { }
+ ngOnInit() {
+ }
+ setInputName(event) {
+ this.inputName = event.target.value;
+ this.inputNameForm.setValue(this.inputName);
+ }
+
+}
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import {DeclareInputComponent} from "./declare-input.component";
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule
+ ],
+ declarations: [DeclareInputComponent],
+ exports: [DeclareInputComponent],
+ entryComponents: [
+ DeclareInputComponent
+ ],
+})
+
+export class DeclareInputModule { }
<span class="sprite search-icon" data-tests-id="search-button"></span>
<filter-properties-assignment *ngIf="isPropertiesTabSelected" #advanceSearch class="advance-search" [componentType]="component.componentType" (searchProperties)="searchPropertiesInstances($event)"></filter-properties-assignment>
</div>
- <button (click)="selectToscaFunctionAndValues()"
+ <button (click)="openToscaGetFunctionModal()"
*ngIf="isPropertiesTabSelected && !isSelf()"
[disabled]="(checkedPropertiesCount != 1 || isReadonly || hasChangedData) && !enableToscaFunction"
class="tlv-btn blue declare-button"
data-tests-id="declare-button select-tosca-function">{{'TOSCA_FUNCTION_LABEL' | translate}}</button>
- <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData" (click)="declareProperties()" data-tests-id="declare-button declare-input">Declare Input</button>
+ <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData" (click)="openAddInputNameAndDeclareInputModal()" data-tests-id="declare-button declare-input">Declare Input</button>
<button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly || hasChangedData || isSelf()" (click)="declarePropertiesToPolicies()" data-tests-id="declare-button declare-policy">Declare Policy</button>
<button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || checkedChildPropertiesCount || isReadonly || hasChangedData" (click)="declareListProperties()" data-tests-id="declare-but($event)ton declare-list-input">Create List Input</button>
</div>
import {TranslateService} from "../../shared/translator/translate.service";
import {ToscaFunction} from "../../../models/tosca-function";
import {SubPropertyToscaFunction} from "../../../models/sub-property-tosca-function";
+import {DeclareInputComponent} from "./declare-input/declare-input.component";
const SERVICE_SELF_TITLE = "SELF";
@Component({
}
/*** DECLARE PROPERTIES/INPUTS ***/
- declareProperties = (): void => {
+ declareInputFromProperties = (inputName:string): void => {
console.debug("==>" + this.constructor.name + ": declareProperties");
let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
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]);
}
}, error => {}); //ignore error
};
+ 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, {});
+ modal.instance.open();
+ }
+
declareListProperties = (): void => {
// get selected properties
let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap();
newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children
this.dataTypeService.checkForCustomBehavior(newFEProp);
- if (newFEProp.isToscaFunction()) {
- return;
- }
//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 => {
"=========== PROPERTIES ASSIGNMENT DECLARE AS POLICY ===========": "",
"DELETE_POLICY_TITLE": "Delete Policy",
"DELETE_POLICY_MSG": "Are you sure you want to delete policy '{{policyName}}'?",
+ "=========== PROPERTIES ASSIGNMENT DECLARE AS INPUT ===========": "",
+ "ADD_INPUT_NAME_TO_DECLARE": "Enter name of input to be created",
"=========== PROPERTIES ASSIGNMENT TOSCA FUNCTION BUTTON ===========": "",
"TOSCA_FUNCTION_LABEL": "TOSCA function",
"TOSCA_FUNCTION_PROPERTY_SOURCE_LABEL": "Property Source",
private void declareInputToBaseService(ResourcePropertiesAssignmentPage propertiesAssignmentPage, String propertyName) {
propertiesAssignmentPage.selectProperty(propertyName);
- propertiesAssignmentPage.clickOnDeclareInput();
+ propertiesAssignmentPage.clickOnDeclareInput("inputName1");
propertiesAssignmentPage.clickInputTab(propertyName);
- propertiesAssignmentPage.isInputPresent(vfResourceCreateData.getName() + "_" + propertyName);
+ propertiesAssignmentPage.isInputPresent("inputName1");
}
private void declareInputToInstanceProperties(ResourcePropertiesAssignmentPage propertiesAssignmentPage, String propertyName) {
propertiesAssignmentPage.loadCompositionTab();
propertiesAssignmentPage.loadComponentInstanceProperties(vfcs.get(0).getName().concat(" 0"));
propertiesAssignmentPage.selectProperty(propertyName);
- propertiesAssignmentPage.clickOnDeclareInput();
+ propertiesAssignmentPage.clickOnDeclareInput("inputName2");
propertiesAssignmentPage.clickInputTab(propertyName);
- propertiesAssignmentPage.isInputPresent(vfResourceCreateData.getName() + "_" + vfcs.get(0).getName());
+ propertiesAssignmentPage.isInputPresent("inputName2");
}
private CreateVfFlow createVF() {
final Map<String, Object> inputsTosca = getMapEntry(topologyTemplateTosca, "inputs");
assertThat(String.format("'%s' should contain a inputs entry", toscaYaml), inputsTosca, notNullValue());
assertEquals(2, inputsTosca.keySet().stream()
- .filter(s -> (s.contains("resourceSubtype") || s.contains("property1"))).count());
+ .filter(s -> (s.contains("inputName"))).count());
final Map<String, Object> substitutionMapping = getMapEntry(topologyTemplateTosca, "substitution_mappings");
assertThat(String.format("'%s' should contain a substitution_mappings entry", toscaYaml), substitutionMapping,
notNullValue());
public boolean isInputPresent(final String inputName) {
isInputPropertiesTableLoaded();
try {
- waitForElementVisibility(By.xpath(XpathSelector.INPUT_CHECKBOX.formatXpath(inputName)), 5);
+ waitForElementVisibility(By.xpath(XpathSelector.INPUT_TABLE.formatXpath(inputName)), 5);
} catch (final Exception ignored) {
return false;
}
private enum XpathSelector {
INPUT_TAB("//*[contains(@data-tests-id, 'Inputs') and contains(@class, 'active')]"),
PROPERTIES_TABLE("//div[contains(@class,'properties-table')]"),
- INPUT_CHECKBOX("//checkbox[@data-tests-id='%s']"),
+ INPUT_TABLE("//*[contains(@class, 'property-name') and text()='%s']"),
NO_DATA_MESSAGE("//div[contains(@class,'no-data') and text()='No data to display']"),
PROPERTY_SAVE_BTN("//button[@data-tests-id='properties-save-button']"),
PROPERTY_SAVE_MESSAGE("//div[contains(text(), 'Successfully saved')]"),
resourcePropertiesAssignmentTab.loadComponentInstanceProperties(instanceName);
}
- public void clickOnDeclareInput(){
- resourcePropertiesAssignmentTab.clickOnDeclareInput();
+ public void clickOnDeclareInput(final String inputName){
+ resourcePropertiesAssignmentTab.clickOnDeclareInput(inputName);
}
public void loadCompositionTab(){
waitToBeClickable(By.xpath(ResourcePropertiesAssignmentTab.XpathSelector.INSTANCE_SPAN.getXpath(instanceName))).click();
}
- public void clickOnDeclareInput(){
+ public void clickOnDeclareInput(final String inputName){
waitToBeClickable(By.xpath(ResourcePropertiesAssignmentTab.XpathSelector.DECLARE_INPUT_BTN.getXpath())).click();
+ declareInputWithSetInputName(inputName);
+ }
+
+ private void declareInputWithSetInputName(final String inputName) {
+ final By setInputNameLocator = By.xpath(XpathSelector.SET_INPUT_NAME_FIELD.getXpath());
+ final WebElement inputNameField = waitForElementVisibility(setInputNameLocator, 5);
+ inputNameField.sendKeys(inputName);
+ waitToBeClickable(By.xpath(XpathSelector.INPUT_NAME_SAVE_BTN.getXpath())).click();
}
public void loadCompositionTab(){
SOFTWARE_VERSION_PROPERTY_CHECKBOX("software_versions", "//checkbox[@data-tests-id='%s']"),
SOFTWARE_VERSION_INPUT("value-prop-software_versions", "//input[starts-with(@data-tests-id,'%s')]"),
PROPERTY_CHECKBOX("//checkbox[@data-tests-id='%s']"),
+ SET_INPUT_NAME_FIELD("//*[@id=\"myText\"]"),
PROPERTY_SAVE_BTN("properties-save-button", "//button[@data-tests-id='%s']"),
PROPERTY_ADD_RIGHT_COLUMN_DIV("right-column", "//div[@class='%s']"),
PROPERTY_ADD_BTN("add-btn", "//div[contains(@class,'%s')]"),
PROPERTY_TYPES("//*[contains(@data-tests-id, 'propertyType')]"),
PROPERTY_NAMES("//*[contains(@data-tests-id, 'propertyName')]"),
DECLARE_INPUT_BTN("declare-button declare-input", "//button[@data-tests-id='%s']"),
+ INPUT_NAME_SAVE_BTN("//button[@data-tests-id='Save']"),
COMPOSITION_TAB("Composition", "//div[contains(@class,'tab') and contains(text(), '%s')]"),
INSTANCE_SPAN("//span[@data-tests-id='%s']");