Edit properties of non-normative data types 76/133176/8
authorfranciscovila <javier.paradela.vila@est.tech>
Tue, 7 Feb 2023 17:00:03 +0000 (17:00 +0000)
committerMichael Morris <michael.morris@est.tech>
Thu, 9 Feb 2023 19:17:04 +0000 (19:17 +0000)
Develop all necessary changes in the UI to allow editing of non-normative data types

Issue-ID: SDC-4373
Signed-off-by: franciscovila <javier.paradela.vila@est.tech>
Change-Id: I37749fd3d2992f3134a09c07bb43c0208ce12a23

catalog-be/src/main/java/org/openecomp/sdc/be/servlets/DataTypeServlet.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/DataTypeOperation.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/PropertyOperation.java
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.html
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/add-property/add-property.component.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.html
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.ts
catalog-ui/src/app/ng2/services/data-type.service.ts
catalog-ui/src/assets/languages/en_US.json

index 0f62571..05c1fb7 100644 (file)
@@ -38,6 +38,7 @@ import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -169,6 +170,45 @@ public class DataTypeServlet extends BeGenericServlet {
         return Response.status(Status.CREATED).entity(property).build();
     }
 
+    @PUT
+    @Path("{id}/properties")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Update a property in the given data type", method = "POST", description = "Update a property in the given data type",
+        responses = {
+            @ApiResponse(content = @Content(schema = @Schema(implementation = PropertyDefinitionDto.class))),
+            @ApiResponse(responseCode = "201", description = "Property updated in the data type"),
+            @ApiResponse(responseCode = "400", description = "Invalid payload"),
+            @ApiResponse(responseCode = "403", description = "Restricted operation"),
+            @ApiResponse(responseCode = "404", description = "Data type not found")
+        })
+    @PermissionAllowed(AafPermission.PermNames.INTERNAL_ALL_VALUE)
+    public Response updateProperty(@Parameter(in = ParameterIn.PATH, required = true, description = "The data type id")
+                                   @PathParam("id") final String id,
+                                   @RequestBody(description = "Property to update", required = true) final PropertyDefinitionDto propertyDefinitionDto) {
+        Optional<DataTypeDataDefinition> dataTypeOptional = dataTypeOperation.getDataTypeByUid(id);
+        dataTypeOptional.orElseThrow(() -> {
+            throw new OperationException(ActionStatus.DATA_TYPE_NOT_FOUND, String.format("Failed to find data type '%s'", id));
+        });
+        DataTypeDataDefinition dataType = dataTypeOptional.get();
+        String model = dataType.getModel();
+        Optional<DataTypeDataDefinition> propertyDataType = dataTypeOperation.getDataTypeByNameAndModel(propertyDefinitionDto.getType(), model);
+        if (propertyDataType.isEmpty()) {
+            if (StringUtils.isEmpty(model)) {
+                model = Constants.DEFAULT_MODEL_NAME;
+            }
+            throw new OperationException(ActionStatus.INVALID_MODEL,
+                String.format("Property model is not the same as the data type model. Must be '%s'", model));
+        }
+        if (StringUtils.isEmpty(dataType.getModel())) {
+            dataType.setModel(Constants.DEFAULT_MODEL_NAME);
+        }
+        final PropertyDefinitionDto property = dataTypeOperation.updateProperty(id, propertyDefinitionDto);
+        dataTypeOperation.addPropertyToAdditionalTypeDataType(dataType, property);
+        dataTypeBusinessLogic.updateApplicationDataTypeCache(id);
+        return Response.status(Status.CREATED).entity(property).build();
+    }
+
     @GET
     @Path("{dataTypeName}/models")
     @Consumes(MediaType.APPLICATION_JSON)
index bbc70f6..391add9 100644 (file)
@@ -281,6 +281,28 @@ public class DataTypeOperation extends AbstractOperation {
         return PropertyDefinitionDtoMapper.mapFrom(propertyDataDefinition);
     }
 
+    public PropertyDefinitionDto updateProperty(final String dataTypeId, final PropertyDefinitionDto propertyDefinitionDto) {
+        final String propertyName = propertyDefinitionDto.getName();
+        LOGGER.debug("Updating property '{}' to data type '{}'.", propertyName, dataTypeId);
+
+        getDataTypeByUid(dataTypeId).orElseThrow(DataTypeOperationExceptionSupplier.dataTypeNotFound(dataTypeId));
+
+        final Either<PropertyDefinition, JanusGraphOperationStatus> resultEither =
+            propertyOperation.updatePropertyAssociatedToNode(NodeTypeEnum.DataType, dataTypeId, PropertyDefinitionDtoMapper.mapTo(propertyDefinitionDto));
+        if (resultEither.isRight()) {
+            final JanusGraphOperationStatus status = resultEither.right().value();
+            LOGGER.debug("Could not update property '{}' on data type '{}'. JanusGraph status is '{}'", propertyName, dataTypeId, status);
+            if (status == JanusGraphOperationStatus.JANUSGRAPH_SCHEMA_VIOLATION) {
+                throw DataTypeOperationExceptionSupplier.dataTypePropertyAlreadyExists(dataTypeId, propertyName).get();
+            }
+            LOGGER.error("Could not update property '{}' on data type '{}'. JanusGraph status is '{}'", propertyName, dataTypeId, status);
+            throw DataTypeOperationExceptionSupplier.unexpectedErrorWhileCreatingProperty(dataTypeId, propertyName).get();
+        }
+        LOGGER.debug("Property '{}' was updated in data type '{}'.", propertyName, dataTypeId);
+        final PropertyDefinition propertyData = resultEither.left().value();
+        return PropertyDefinitionDtoMapper.mapFrom(propertyData);
+    }
+
     public void addPropertyToAdditionalTypeDataType(DataTypeDataDefinition dataTypeDataDefinition, PropertyDefinitionDto property) {
         modelOperation.addPropertyToAdditionalType(ElementTypeEnum.DATA_TYPE, property, dataTypeDataDefinition.getModel(), dataTypeDataDefinition.getName());
     }
index 0b8d2c8..5164743 100644 (file)
@@ -382,6 +382,23 @@ public class PropertyOperation extends AbstractOperation implements IPropertyOpe
         return Either.left(createNodeResult.left().value());
     }
 
+    public Either<PropertyDefinition, JanusGraphOperationStatus> updatePropertyAssociatedToNode(NodeTypeEnum nodeType, String uniqueId,
+                                                                                                        PropertyDefinition newProperty) {
+        Either<Map<String, PropertyDefinition>, JanusGraphOperationStatus> oldPropertiesRes = findPropertiesOfNode(nodeType, uniqueId);
+
+        if (oldPropertiesRes.isRight()) {
+            return Either.right(oldPropertiesRes.right().value());
+        } else {
+            Map<String, PropertyDefinition> oldProperties = oldPropertiesRes.left().value();
+            PropertyDefinition oldPropDef = oldProperties.get(newProperty.getName());
+            JanusGraphOperationStatus status = updateOldProperty(newProperty, oldPropDef);
+            if (status != JanusGraphOperationStatus.OK) {
+                return Either.right(status);
+            }
+        }
+        return Either.left(newProperty);
+    }
+
     public Either<Map<String, PropertyDefinition>, JanusGraphOperationStatus> findPropertiesOfNode(NodeTypeEnum nodeType, String uniqueId) {
         Map<String, PropertyDefinition> resourceProps = new HashMap<>();
         Either<List<ImmutablePair<PropertyData, GraphEdge>>, JanusGraphOperationStatus> childrenNodes = janusGraphGenericDao
@@ -501,6 +518,7 @@ public class PropertyOperation extends AbstractOperation implements IPropertyOpe
         oldPropDef.setDefaultValue(newPropDef.getDefaultValue());
         oldPropDef.setDescription(newPropDef.getDescription());
         oldPropDef.setRequired(newPropDef.isRequired());
+        oldPropDef.setConstraints(newPropDef.getConstraints());
         // Type is updated to fix possible null type issue in janusGraph DB
         oldPropDef.setType(newPropDef.getType());
     }
index a2e4f48..3ac4f7a 100644 (file)
@@ -6,6 +6,7 @@
       <div class="i-sdc-form-item">
         <label class="i-sdc-form-label required">{{'PROPERTY_NAME_LABEL' | translate}}</label>
         <input class="i-sdc-form-input"
+               [ngClass]="{ 'disabled': property ? true : false }"
                type="text"
                data-tests-id="property-name"
                formControlName="name"
@@ -13,7 +14,7 @@
       </div>
       <div class="i-sdc-form-item">
         <label class="i-sdc-form-label required">{{'PROPERTY_TYPE_LABEL' | translate}}</label>
-        <select formControlName="type" (change)="onTypeChange()" [attr.disabled]="readOnly ? readOnly : null">
+          <select formControlName="type" (change)="onTypeChange()" [ngClass]="{ 'disabled': property ? true : false }">
           <option [ngValue]="null">{{'GENERAL_LABEL_SELECT' | translate}}</option>
           <option *ngFor="let type of typeList"
                   [ngValue]="type">{{type}}</option>
index 56db2ce..8eb04c0 100644 (file)
@@ -45,7 +45,7 @@ export class AddPropertyComponent implements OnInit, OnDestroy {
     private validConstraints: boolean = true;
     private valueChangesSub: Subscription;
     private descriptionForm: FormControl = new FormControl(undefined);
-    private requiredForm: FormControl = new FormControl(false, Validators.required);
+    private requiredForm: FormControl = new FormControl(false);
     nameForm: FormControl = new FormControl(undefined, [Validators.required]);
     typeForm: FormControl = new FormControl(undefined, Validators.required);
     schemaForm: FormControl = new FormControl(undefined, (control: AbstractControl): ValidationErrors | null => {
@@ -163,9 +163,10 @@ export class AddPropertyComponent implements OnInit, OnDestroy {
 
     private emitValidityChange(): void {
         const isValid: boolean = this.formGroup.valid;
+        this.findInvalidControls().forEach(name => console.error("Validation error in field: " + name));
         this.onValidityChange.emit({
             isValid: isValid && this.validConstraints,
-            property: isValid ? this.buildPropertyFromForm() : undefined
+            property: isValid ? this.buildPropertyFromForm() : this.nameForm.value
         });
     }
 
@@ -173,6 +174,7 @@ export class AddPropertyComponent implements OnInit, OnDestroy {
         const property = new PropertyBEModel();
         property.name = this.nameForm.value;
         property.type = this.typeForm.value;
+        property.required = this.requiredForm.value;
         property.constraints = this.constraintsForm.value;
         if (this.schemaForm.value) {
             property.schemaType = this.schemaForm.value;
@@ -241,15 +243,27 @@ export class AddPropertyComponent implements OnInit, OnDestroy {
             }
             this.property.constraints = constraints.constraints;
         }
-        else {
-            this.constraintsForm.setValue(constraints.constraints);
-        }
+
+        this.constraintsForm.setValue(constraints.constraints);
+
         this.validConstraints = constraints.valid;
+        let formValid = constraints.valid && this.findInvalidControls().length === 0;
         this.onValidityChange.emit({
-            isValid: constraints.valid,
-            property: constraints.valid ? this.buildPropertyFromForm() : undefined
+            isValid: formValid,
+            property: formValid ? this.buildPropertyFromForm() : undefined
         });
     }
+
+    findInvalidControls() {
+        const invalid = [];
+        const controls = this.formGroup.controls;
+        for (const name in controls) {
+            if (controls[name].invalid) {
+                invalid.push(name);
+            }
+        }
+        return invalid;
+    }
 }
 
 export class PropertyValidationEvent {
index ec67a02..e657520 100644 (file)
@@ -38,9 +38,9 @@
         <div *ngIf="filteredProperties.length === 0" class="no-row-text">
           {{'PROPERTY_LIST_EMPTY_MESSAGE' | translate}}
         </div>
-        <div *ngFor="let property of filteredProperties" [attr.data-tests-id]="'property-row-' + property.name" class="flex-container data-row" (click)="onRowClick(property)">
+        <div *ngFor="let property of filteredProperties" [attr.data-tests-id]="'property-row-' + property.name" class="flex-container data-row" (click)="onNameClick(property)">
           <div class="table-col-general flex-item text" [title]="property.name">
-            <a [attr.data-tests-id]="'property-name-' + property.name" [ngClass]="{'disabled': isViewOnly}">{{property.name}}</a>
+            <a [attr.data-tests-id]="'property-name-' + property.name" [ngClass]="{'disabled': false}">{{property.name}}</a>
           </div>
           <div class="table-col-general flex-item text" [title]="property.type">
             <span [attr.data-tests-id]="'property-type-' + property.name">{{property.type}}</span>
index 83651fc..60edd13 100644 (file)
@@ -132,12 +132,20 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
         this.filter();
     }
 
+    private updateProperty(oldProperty: PropertyBEModel, newProperty: PropertyBEModel) {
+        this.properties.forEach((value,index)=>{
+            if(value.name == oldProperty.name) this.properties.splice(index,1);
+        });
+        this.properties.push(newProperty);
+        this.filter();
+    }
+
     onClickAddProperty() {
-        this.openAddPropertyModal();
+        this.openAddPropertyModal(null, false);
     }
 
     private openAddPropertyModal(property?: PropertyBEModel, readOnly: boolean = false) {
-        const modalTitle = this.translateService.translate('PROPERTY_ADD_MODAL_TITLE');
+        const modalTitle = this.translateService.translate(property ? 'PROPERTY_EDIT_MODAL_TITLE' : 'PROPERTY_ADD_MODAL_TITLE');
         const modalButtons = [];
         let disableSaveButtonFlag = true;
         let propertyFromModal: PropertyBEModel = undefined;
@@ -156,9 +164,16 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
             modalButtons.push(new ButtonModel(this.translateService.translate('MODAL_SAVE'), 'blue',
                 () => {
                     disableSaveButtonFlag = true;
-                    this.dataTypeService.createProperty(this.dataType.uniqueId, propertyFromModal).subscribe(property => {
-                        this.addProperty(new PropertyBEModel(property));
-                    });
+                    if (property) {
+                        this.dataTypeService.updateProperty(this.dataType.uniqueId, propertyFromModal).subscribe(property => {
+                            this.updateProperty(propertyFromModal, new PropertyBEModel(property));
+                        });
+                    }
+                    else {
+                        this.dataTypeService.createProperty(this.dataType.uniqueId, propertyFromModal).subscribe(property => {
+                            this.addProperty(new PropertyBEModel(property));
+                        });
+                    }
                     this.modalService.closeCurrentModal();
                 },
                 (): boolean => {
@@ -185,8 +200,8 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
         modal.instance.open();
     }
 
-    onRowClick(property: PropertyBEModel) {
-        this.openAddPropertyModal(property, true);
+    onNameClick(property: PropertyBEModel) {
+        this.openAddPropertyModal(property, this.isViewOnly);
     }
 
     private showPropertiesMap(properties: Array<PropertyBEModel>): void {
index 7e18d0a..38714c9 100644 (file)
@@ -92,6 +92,11 @@ export class DataTypeService {
         return this.httpClient.post<PropertyBEModel>(url, property);
     }
 
+    public updateProperty(id: string, property: PropertyBEModel): Observable<PropertyBEModel> {
+        const url = `${this.dataTypeUrl}/${id}/properties`;
+        return this.httpClient.put<PropertyBEModel>(url, property);
+    }
+
     public createImportedType(model: string, importingFile: File): Observable<any> {
         const url = `${this.dataTypeUploadUrl}/datatypesyaml`;
         const formData = new FormData();
index bb2ebd5..a98bf4c 100644 (file)
   "PROPERTY_LIST_EMPTY_MESSAGE": "There are no properties to display",
   "PROPERTY_SHOWING_LABEL": "Showing Properties",
   "PROPERTY_ADD_MODAL_TITLE": "Add Property",
+  "PROPERTY_EDIT_MODAL_TITLE": "Update Property",
   "PROPERTY_VIEW_MODAL_TITLE": "View Property",
   "PROPERTY_DESCRIPTION_LABEL": "Description",
   "PROPERTY_DEFAULT_VALUE_LABEL": "Default Value",