Import data type in UI 42/132842/19
authorfranciscovila <javier.paradela.vila@est.tech>
Thu, 24 Nov 2022 10:29:04 +0000 (10:29 +0000)
committerMichael Morris <michael.morris@est.tech>
Thu, 26 Jan 2023 23:32:10 +0000 (23:32 +0000)
Develop all necessary changes in the UI to allow importing a data type from a yaml file

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

34 files changed:
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/CommonImportManager.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/DataTypeServlet.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/TypesUploadServlet.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/ModelOperation.java
catalog-ui/src/app/app.ts
catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.html [new file with mode: 0644]
catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.less [new file with mode: 0644]
catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.spec.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/components/ui/ui-elements.module.ts
catalog-ui/src/app/ng2/pages/home/__snapshots__/home.component.spec.ts.snap
catalog-ui/src/app/ng2/pages/home/home.component.html
catalog-ui/src/app/ng2/pages/home/home.component.spec.ts
catalog-ui/src/app/ng2/pages/home/home.component.ts
catalog-ui/src/app/ng2/pages/home/home.module.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-general/type-workspace-general.component.html
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-general/type-workspace-general.component.spec.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-general/type-workspace-general.component.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.spec.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace-properties/type-workspace-properties.component.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.html
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.spec.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.component.ts
catalog-ui/src/app/ng2/pages/type-workspace/type-workspace.module.ts
catalog-ui/src/app/ng2/pages/type-workspace/workspace-menu/workspace-menu.component.html
catalog-ui/src/app/ng2/pages/type-workspace/workspace-menu/workspace-menu.component.spec.ts
catalog-ui/src/app/ng2/pages/type-workspace/workspace-menu/workspace-menu.component.ts
catalog-ui/src/app/ng2/services/data-type.service.ts
catalog-ui/src/app/ng2/services/model.service.ts
catalog-ui/src/app/services/data-types-service.ts
catalog-ui/src/app/utils/service-data-type-reader.ts [new file with mode: 0644]
catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
catalog-ui/src/assets/languages/en_US.json

index 474df3f..bfbebf3 100644 (file)
@@ -127,7 +127,10 @@ public class CommonImportManager {
     private Map<String, Object> convertToFieldMap(String elementTypesYml) {
         Map<String, Object> toscaJson = null;
         try {
-            toscaJson = (Map<String, Object>) new Yaml().load(elementTypesYml);
+            toscaJson = new Yaml().load(elementTypesYml);
+            if (toscaJson.containsKey("data_types")){
+                toscaJson = (Map<String, Object>) toscaJson.get("data_types");
+            }
         } catch (Exception e) {
             log.debug("Failed to yaml file {}", elementTypesYml, e);
         }
@@ -276,11 +279,9 @@ public class CommonImportManager {
                 eitherResult = handleType(elementType, validator, elementInfoGetter, elementFetcher, elementAdder, elementUpgrader)
                     .left()
                     .map(elem -> append(createdElementTypes, elem));
-
                 if (eitherResult.isRight()) {
                     break;
                 }
-
                 if (!elementTypeItr.hasNext()) {
                     log.info("all {} were created successfully!!!", elementType);
                 }
@@ -295,7 +296,6 @@ public class CommonImportManager {
                 propertyOperation.getJanusGraphGenericDao().rollback();
             }
         }
-
         return eitherResult;
     }
 
index 8139237..a7327bf 100644 (file)
@@ -163,4 +163,19 @@ public class DataTypeServlet extends BeGenericServlet {
         return Response.status(Status.CREATED).entity(property).build();
     }
 
+    @GET
+    @Path("{dataTypeName}/models")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(description = "Get models for type", method = "GET", summary = "Returns list of models for type", responses = {
+        @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))),
+        @ApiResponse(responseCode = "200", description = "dataTypeModels"), @ApiResponse(responseCode = "403", description = "Restricted operation"),
+        @ApiResponse(responseCode = "400", description = "Invalid content / Missing content"),
+        @ApiResponse(responseCode = "403", description = "Restricted operation"),
+        @ApiResponse(responseCode = "404", description = "Data type not found")})
+    @PermissionAllowed(AafPermission.PermNames.INTERNAL_ALL_VALUE)
+    public Response getDataTypeModels(@PathParam("dataTypeName") String dataTypeName) {
+        return buildOkResponse(getComponentsUtils().getResponseFormat(ActionStatus.OK),
+            gson.toJson(dataTypeOperation.getAllDataTypeModels(dataTypeName)));
+    }
 }
index 786c033..4fcc3c0 100644 (file)
@@ -31,7 +31,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.servers.Server;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -49,6 +51,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.glassfish.jersey.media.multipart.FormDataParam;
+import org.jetbrains.annotations.NotNull;
 import org.openecomp.sdc.be.components.impl.ArtifactTypeImportManager;
 import org.openecomp.sdc.be.components.impl.CapabilityTypeImportManager;
 import org.openecomp.sdc.be.components.impl.CategoriesImportManager;
@@ -218,7 +221,7 @@ public class TypesUploadServlet extends AbstractValidationsServlet {
 
     @POST
     @Path("/datatypes")
-    @Operation(description = "Create Categories from yaml", method = "POST", summary = "Returns created data types", responses = {
+    @Operation(description = "Create Data Types from zip", method = "POST", summary = "Returns created data types", responses = {
         @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))),
         @ApiResponse(responseCode = "201", description = "Data types created"),
         @ApiResponse(responseCode = "403", description = "Restricted operation"),
@@ -233,6 +236,23 @@ public class TypesUploadServlet extends AbstractValidationsServlet {
             includeToModelDefaultImports);
     }
 
+    @POST
+    @Path("/datatypesyaml")
+    @Operation(description = "Create Data Types from yaml", method = "POST", summary = "Returns created data types", responses = {
+        @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))),
+        @ApiResponse(responseCode = "201", description = "Data types created"),
+        @ApiResponse(responseCode = "403", description = "Restricted operation"),
+        @ApiResponse(responseCode = "400", description = "Invalid content / Missing content"),
+        @ApiResponse(responseCode = "409", description = "Data types already exist")})
+    @PermissionAllowed(AafPermission.PermNames.INTERNAL_ALL_VALUE)
+    public Response uploadDataTypesYaml(@Parameter(description = "FileInputStream") @FormDataParam("dataTypesYaml") File file,
+                                    @Context final HttpServletRequest request, @HeaderParam("USER_ID") String creator,
+                                    @Parameter(description = "model") @FormDataParam("model") String modelName,
+                                    @Parameter(description = "includeToModelImport") @FormDataParam("includeToModelImport") boolean includeToModelDefaultImports) {
+        return uploadElementTypeServletLogicYaml(this::createDataTypes, file, request, creator, NodeTypeEnum.DataType.getName(), modelName,
+            includeToModelDefaultImports);
+    }
+
     @POST
     @Path("/grouptypes")
     @Operation(description = "Create GroupTypes from yaml", method = "POST", summary = "Returns created group types", responses = {
@@ -323,6 +343,42 @@ public class TypesUploadServlet extends AbstractValidationsServlet {
         }
     }
 
+    private Response uploadElementTypeServletLogicYaml(final ConsumerFourParam<Wrapper<Response>, String, String, Boolean> createElementsMethod,
+                                                   final File file, final HttpServletRequest request, final String creator,
+                                                   final String elementTypeName, final String modelName, final boolean includeToModelDefaultImports) {
+        init();
+        final String userId = initHeaderParam(creator, request, Constants.USER_ID_HEADER);
+        try {
+            final Wrapper<String> yamlStringWrapper = new Wrapper<>();
+            final String url = request.getMethod() + " " + request.getRequestURI();
+            log.debug(START_HANDLE_REQUEST_OF, url);
+            final Wrapper<Response> responseWrapper = doUploadTypeValidations(request, userId, file);
+            if (responseWrapper.isEmpty()) {
+                final String yamlAsString = getFileAsString(file);
+                log.debug("received yaml: {}", yamlAsString);
+                yamlStringWrapper.setInnerElement(yamlAsString);
+            }
+            if (responseWrapper.isEmpty()) {
+                createElementsMethod.accept(responseWrapper, yamlStringWrapper.getInnerElement(), modelName, includeToModelDefaultImports);
+            }
+            return responseWrapper.getInnerElement();
+        } catch (final Exception e) {
+            log.debug(CREATE_FAILED_WITH_EXCEPTION, elementTypeName, e);
+            BeEcompErrorManager.getInstance().logBeRestApiGeneralError(CREATE + elementTypeName);
+            return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.GENERAL_ERROR));
+        }
+    }
+
+    @NotNull
+    private String getFileAsString(File file) throws IOException {
+        FileInputStream fl = new FileInputStream(file);
+        byte[] arr = new byte[(int) file.length()];
+        fl.read(arr);
+        fl.close();
+        final String yamlAsString = new String(arr, StandardCharsets.UTF_8);
+        return yamlAsString;
+    }
+
     private Wrapper<Response> doUploadTypeValidations(final HttpServletRequest request, String userId, File file) {
         Wrapper<Response> responseWrapper = new Wrapper<>();
         Wrapper<User> userWrapper = new Wrapper<>();
index 36dcaf4..7d01f3f 100644 (file)
@@ -29,6 +29,7 @@ import java.util.Optional;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutableTriple;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.janusgraph.core.JanusGraph;
@@ -37,6 +38,7 @@ import org.openecomp.sdc.be.config.BeEcompErrorManager.ErrorSeverity;
 import org.openecomp.sdc.be.dao.api.ActionStatus;
 import org.openecomp.sdc.be.dao.janusgraph.HealingJanusGraphGenericDao;
 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
+import org.openecomp.sdc.be.dao.janusgraph.QueryType;
 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
 import org.openecomp.sdc.be.dao.neo4j.GraphPropertiesDictionary;
 import org.openecomp.sdc.be.datatypes.elements.DataTypeDataDefinition;
@@ -124,6 +126,20 @@ public class DataTypeOperation extends AbstractOperation {
         return dataTypesFound;
     }
 
+    public List<String> getAllDataTypeModels(final String dataTypeName) {
+        final List<String> models = new ArrayList<>();
+        ImmutableTriple<QueryType, String, Object> criteria =
+            new ImmutableTriple<>(QueryType.HAS, GraphPropertiesDictionary.NAME.getProperty(), dataTypeName);
+
+        final Either<List<DataTypeData>, JanusGraphOperationStatus> getAllDataTypesForModel =
+            janusGraphGenericDao.getByCriteria(NodeTypeEnum.DataType, DataTypeData.class, List.of(criteria));
+        final var dataTypesValidated = validateDataType(getAllDataTypesForModel, null);
+        for (DataTypeData dataType : dataTypesValidated) {
+            models.add(dataType.getDataTypeDataDefinition().getModel());
+        }
+        return models;
+    }
+
     private List<DataTypeData> getAllDataTypesWithModel(final String modelName) {
         final Either<List<DataTypeData>, JanusGraphOperationStatus> getAllDataTypesByModel = janusGraphGenericDao
             .getByCriteriaForModel(NodeTypeEnum.DataType, null, modelName, DataTypeData.class);
index 0d462c9..8baa9a7 100644 (file)
@@ -296,7 +296,7 @@ public class ModelOperation {
             rebuiltModelImportList = new ArrayList<>(modelImportList);
         }
 
-        final Map<String, Object> typesYamlMap = new Yaml().load(typesYaml);
+        final Map<String, Object> typesYamlMap = new Yaml().loadAs(typesYaml, Map.class);
         removeExistingTypesFromDefaultImports(elementTypeEnum, typesYamlMap, rebuiltModelImportList);
 
         final Map<String, Object> originalContent = new Yaml().load(additionalTypeDefinitionsImport.getContent());
index 76c1d28..296b63b 100644 (file)
@@ -38,6 +38,7 @@ import {ComponentFactory} from "./utils/component-factory";
 import {Component} from "./models/components/component";
 import {IUserProperties} from "./models/user";
 import {WorkspaceService} from "./ng2/pages/workspace/workspace.service";
+import {TypeWorkspaceGeneralComponent} from "./ng2/pages/type-workspace/type-workspace-general/type-workspace-general.component";
 
 let moduleName: string = 'sdcApp';
 let viewModelsModuleName: string = 'Sdc.ViewModels';
@@ -213,8 +214,11 @@ ng1appModule.config([
     $stateProvider.state(
         States.TYPE_WORKSPACE, {
           url: '/:previousState/type-workspace/:type/:id/:subPage',
-          template: '<app-type-workspace></app-type-workspace>',
-      }
+          params: {
+              'importedFile': null
+          },
+          template: '<app-type-workspace></app-type-workspace>'
+        }
     );
 
     $stateProvider.state(
diff --git a/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.html b/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.html
new file mode 100644 (file)
index 0000000..37439f8
--- /dev/null
@@ -0,0 +1,51 @@
+<!--
+  ~ -
+  ~  ============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="import-type-container">
+
+    <form novalidate class="w-sdc-form" name="importForm" validation-on-load form-to-validate="importForm">
+
+        <div class="w-sdc-form-section-container">
+
+                <div class="i-sdc-form-item">
+                    <label class="i-sdc-form-label required">Type</label>
+                    <select class="i-sdc-form-select"
+                            data-ng-class="{'view-mode': true}"
+                            data-ng-disabled="false"
+
+                            data-tests-id="selectType"
+                            data-ng-required="true"
+
+                            >
+                        <option value="" selected>Data Type</option>
+                    </select>
+                </div>
+
+                <div class="i-sdc-form-item">
+                  <label class="i-sdc-form-label required">Select File to Import
+                    <input (change)="onFileChange($event)" type="file" class="i-sdc-dashboard-item-upload-input"/>
+                  </label>
+                </div>
+
+            </div>
+
+    </form>
+
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.less b/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.less
new file mode 100644 (file)
index 0000000..8c391c5
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * -
+ *  ============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-type-container {
+    max-width: 100%;
+}
+
+.default-value-container {
+    overflow: scroll;
+    max-height: 300px;
+    max-width: 100%;
+
+    ul {
+        margin: 0 0 0 20px;
+        list-style: none;
+        line-height: 2em;
+    }
+
+    &::-webkit-scrollbar-track {
+         border: 0;
+     }
+}
+
+.input-label {
+    margin: 0;
+    font-weight: bold;
+}
+
+.input-value {
+    display: flex;
+    flex-flow: row nowrap;
+    gap: 7px;
+
+    input {
+        min-width: 150px;
+        max-width: 250px;
+    }
+}
+
+.empty-value {
+    color: #aaaaaa;
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.spec.ts b/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.spec.ts
new file mode 100644 (file)
index 0000000..9e78563
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * -
+ *  ============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 { ModalImportTypeComponent } from './modal-import-type.component';
+
+describe('ModalImportTypeComponent', () => {
+  let component: ModalImportTypeComponent;
+  let fixture: ComponentFixture<ModalImportTypeComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ModalImportTypeComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ModalImportTypeComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.ts b/catalog-ui/src/app/ng2/components/ui/modal-import-type/modal-import-type.component.ts
new file mode 100644 (file)
index 0000000..2c56b2e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * -
+ *  ============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, Inject, OnInit} from '@angular/core';
+
+@Component({
+  selector: 'app-modal-import-type',
+  templateUrl: './modal-import-type.component.html',
+  styleUrls: ['./modal-import-type.component.less']
+})
+export class ModalImportTypeComponent implements OnInit {
+
+  file:File = null;
+
+  constructor() {}
+
+  ngOnInit() {
+  }
+
+  onFileChange(event: any) {
+      this.file = event.target.files[0];
+  }
+
+  public onImportDataType(file: any): void {
+    if (file && file.filename) {
+        console.log("file: " + file.filename);
+    }
+  }
+
+}
index cdb173c..8e0a66c 100644 (file)
@@ -44,6 +44,7 @@ import {FileOpenerComponent} from "./file-opener/file-opener.component";
 import {DownloadArtifactComponent} from "app/ng2/components/ui/download-artifact/download-artifact.component";
 import {SdcElementIconComponent} from "./sdc-element-icon/sdc-element-icon.component";
 import {PanelWrapperComponent} from "./panel-wrapper/panel-wrapper.component";
+import { ModalImportTypeComponent } from './modal-import-type/modal-import-type.component';
 
 @NgModule({
     declarations: [
@@ -56,7 +57,8 @@ import {PanelWrapperComponent} from "./panel-wrapper/panel-wrapper.component";
         FileOpenerComponent,
         SdcElementIconComponent,
         DownloadArtifactComponent,
-        PanelWrapperComponent
+        PanelWrapperComponent,
+        ModalImportTypeComponent
     ],
 
     imports: [
@@ -97,7 +99,8 @@ import {PanelWrapperComponent} from "./panel-wrapper/panel-wrapper.component";
         SdcElementIconComponent,
         FileOpenerComponent,
         DownloadArtifactComponent,
-        PanelWrapperComponent
+        PanelWrapperComponent,
+        ModalImportTypeComponent
     ],
     entryComponents: [SearchWithAutoCompleteComponent, SdcElementIconComponent, PaletteAnimationComponent]
 })
index 42686c1..dd864d4 100644 (file)
@@ -12,7 +12,9 @@ exports[`home component should match current snapshot 1`] = `
   isDefaultFilter={[Function Function]}
   loaderService={[Function Object]}
   modalService={[Function Object]}
+  modalServiceSdc="undefined"
   modalsHandler={[Function Object]}
+  openModalImportType={[Function Function]}
   resourceService={[Function Object]}
   sdcConfig={[Function Object]}
   sdcMenu={[Function Object]}
index 0c2e41e..b16b30b 100644 (file)
@@ -58,6 +58,7 @@
                                 (fileUpload)="onImportService($event)"
                                 [convertToBase64]="true"
                             ></sdc-button-file-opener>
+                            <sdc-button *ngIf="roles[user.role].dashboard.showCreateNew" testId="importTypebutton" size="medium" type="secondary" text="Import Type" (click)="openModalImportType()"></sdc-button>
                         </div>
                     </div>
                 </div>
index 1c03790..2642b9e 100644 (file)
@@ -7,7 +7,7 @@ import {HomeComponent} from "./home.component";
 import {ConfigureFn, configureTests} from "../../../../jest/test-config.helper";
 import {NO_ERRORS_SCHEMA} from "@angular/core";
 import {TranslateService} from "../../shared/translator/translate.service";
-import {AuthenticationService, CacheService, HomeService, ImportVSPService, ResourceServiceNg2} from '../../../../app/services-ng2';
+import {AuthenticationService, CacheService, HomeService, ImportVSPService, ModalService, ResourceServiceNg2} from '../../../../app/services-ng2';
 import {ModalsHandler} from "../../../../app/utils";
 import {SdcUiServices} from "onap-ui-angular";
 import {ComponentType, ResourceType} from "../../../utils/constants";
@@ -23,6 +23,7 @@ describe('home component', () => {
     let importVspService: Partial<ImportVSPService>;
     let mockStateService;
     let modalServiceMock :Partial<SdcUiServices.ModalService>;
+    let modalServiceMock_ :Partial<ModalService>;
     let translateServiceMock : Partial<TranslateService>;
     let foldersItemsMenuMock;
     let homeFilterMock :Partial<HomeFilter>;
@@ -79,6 +80,7 @@ describe('home component', () => {
                         {provide: TranslateService, useValue: translateServiceMock},
                         {provide: ModalsHandler, useValue: {}},
                         {provide: SdcUiServices.ModalService, useValue: modalServiceMock},
+                        {provide: ModalService, useValue: modalServiceMock_},
                         {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock},
                         {provide: ImportVSPService, useValue: {}},
                         {provide: ResourceServiceNg2, useValue: resourceServiceNg2Mock}
index 784823e..666b36e 100644 (file)
  */
 'use strict';
 import {Component as NgComponent, Inject, OnInit} from '@angular/core';
-import {Component, ComponentMetadata, IConfigRoles, IUserProperties, Resource, Service} from 'app/models';
+import {
+    ButtonModel,
+    Component,
+    ComponentMetadata,
+    IConfigRoles,
+    IUserProperties,
+    ModalModel,
+    Resource,
+    Service
+} from 'app/models';
+import {ModalService} from "../../services/modal.service";
 import {HomeFilter} from 'app/models/home-filter';
 import {AuthenticationService, CacheService, HomeService, ResourceServiceNg2} from 'app/services-ng2';
-import {ComponentState, ModalsHandler} from 'app/utils';
+import {ComponentState, ModalsHandler, States} from 'app/utils';
 import {SdcUiServices} from 'onap-ui-angular';
 import {CHANGE_COMPONENT_CSAR_VERSION_FLAG, ComponentType, ResourceType} from '../../../utils/constants';
 import {ImportVSPService} from '../../components/modals/onboarding-modal/import-vsp.service';
@@ -33,6 +43,7 @@ import {TranslateService} from '../../shared/translator/translate.service';
 import {FoldersItemsMenu, FoldersItemsMenuGroup, FoldersMenu} from './folders';
 import {ImportVSPdata} from "../../components/modals/onboarding-modal/onboarding-modal.component";
 import {DataTypeCatalogComponent} from "../../../models/data-type-catalog-component";
+import {ModalImportTypeComponent} from "../../components/ui/modal-import-type/modal-import-type.component";
 
 @NgComponent({
     selector: 'home-page',
@@ -64,6 +75,7 @@ export class HomeComponent implements OnInit {
         private translateService: TranslateService,
         private modalsHandler: ModalsHandler,
         private modalService: SdcUiServices.ModalService,
+        private modalServiceSdc: ModalService,
         private loaderService: SdcUiServices.LoaderService,
         private importVSPService: ImportVSPService,
         private resourceService: ResourceServiceNg2
@@ -398,4 +410,41 @@ export class HomeComponent implements OnInit {
         this.notificationIconCallback = this.notificationIconCallback.bind(this);
     }
 
+    openModalImportType = () => {
+        let modalTitle = 'Import Type';
+        let modal = this.modalServiceSdc.createCustomModal(new ModalModel(
+            'sm',
+            modalTitle,
+            null,
+            [
+                new ButtonModel('Import', 'blue', () =>
+                { this.uploadDataTypeFile(modal.instance.dynamicContent.instance.file); modal.instance.close();}, () => false),
+                new ButtonModel('Cancel', 'outline grey', () => {
+                    modal.instance.close();
+                }),
+            ],
+            null
+        ));
+        this.modalServiceSdc.addDynamicContentToModal(modal, ModalImportTypeComponent, {});
+        modal.instance.open();
+    }
+
+    private uploadDataTypeFile(file: any): void {
+        if (file && file.name) {
+            // Check that the file has valid extension.
+            const fileExtension: string = file.name.split('.').pop();
+            if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1) {
+                this.$state.go(States.TYPE_WORKSPACE, {
+                    type: "datatype",
+                    subPage: "general",
+                    id: "import",
+                    importedFile: file
+                });
+            } else {
+                const title: string = this.translateService.translate('NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS_TITLE');
+                const message: string = this.translateService.translate('NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS', {extensions: this.sdcConfig.toscaFileExtension});
+                this.modalService.openWarningModal(title, message, 'error-invalid-tosca-ext');
+            }
+        }
+    }
 }
index 1a397b4..acfc299 100644 (file)
@@ -7,6 +7,7 @@ import { GlobalPipesModule } from "../../pipes/global-pipes.module";
 import { TranslateModule } from "../../shared/translator/translate.module";
 import { SdcUiComponentsModule } from "onap-ui-angular";
 import { ResourceServiceNg2 } from "../../services/component-services/resource.service";
+import {ModalImportTypeComponent} from "../../components/ui/modal-import-type/modal-import-type.component";
 
 @NgModule({
     declarations: [
@@ -19,12 +20,14 @@ import { ResourceServiceNg2 } from "../../services/component-services/resource.s
         UiElementsModule,
         GlobalPipesModule,
         TranslateModule
+
     ],
     exports: [
         HomeComponent
     ],
     entryComponents: [
-        HomeComponent
+        HomeComponent,
+        ModalImportTypeComponent
     ],
     providers: [ResourceServiceNg2]
 })
index 877c58b..04c334e 100644 (file)
 
               <div class="i-sdc-form-item">
                 <label class="i-sdc-form-label" [ngClass]="{'required': !isViewOnly}">{{'MODEL_LABEL' | translate}}:</label>
-                <span>{{dataType.model ? dataType.model : DEFAULT_MODEL_NAME}}</span>
+                <span *ngIf="isViewOnly">{{dataType.model ? dataType.model : DEFAULT_MODEL_NAME}}</span>
+                <select *ngIf="!isViewOnly" formControlName="model" (change)="onModelChange()">
+                  <option *ngFor="let model of models"
+                          [value]="model.name">{{model.name}}</option>
+                </select>
               </div>
 
+
               <div class="i-sdc-form-item">
                 <label class="i-sdc-form-label" [ngClass]="{'required': !isViewOnly}">{{'DERIVED_FROM_LABEL' | translate}}:</label>
                 <span>{{dataType.derivedFromName}}</span>
           </div>
 
           <div class="i-sdc-form-item description-field">
-            <label class="i-sdc-form-label" [ngClass]="{'required': !isViewOnly}">{{'DESCRIPTION_LABEL' | translate}}:</label>
-            <textarea class="description"
+            <label class="i-sdc-form-label">{{'DESCRIPTION_LABEL' | translate}}:</label>
+            <textarea class="description" #description
                       formControlName="description"
-                      [ngClass]="{'view-mode': isViewOnly}"
-                      [required]="true"
+                      [ngClass]="{'view-mode': true}"
                       [attr.test-id]="description"
+                      [(ngModel)]="dataType.description"
                       [value]="dataType.description"></textarea>
           </div>
         </div>
index fe7b070..1484954 100644 (file)
@@ -25,22 +25,53 @@ import {TypeWorkspaceGeneralComponent} from './type-workspace-general.component'
 import {ReactiveFormsModule} from "@angular/forms";
 import {TranslateModule} from "../../../shared/translator/translate.module";
 import {TranslateService} from "../../../shared/translator/translate.service";
+import {SdcUiComponentsModule} from "onap-ui-angular/dist";
+import {Observable} from "rxjs/Observable";
+import {DataTypeModel} from "../../../../models/data-types";
+import {DataTypeService} from "../../../services/data-type.service";
+import {ModelService} from "../../../services/model.service";
+import {IWorkspaceViewModelScope} from "../../../../view-models/workspace/workspace-view-model";
+import {IScope} from "angular";
+import {States} from "../../../../utils/constants";
 
 describe('TypeWorkspaceGeneralComponent', () => {
   let component: TypeWorkspaceGeneralComponent;
   let fixture: ComponentFixture<TypeWorkspaceGeneralComponent>;
+  let dataTypeServiceMock: Partial<DataTypeService>;
+  let modelServiceMock: Partial<ModelService>;
   let translateServiceMock: Partial<TranslateService> = {
     'translate': jest.fn()
   };
 
+  let importedFileMock: File = null;
+  let stateParamsMock: Partial<ng.ui.IStateParamsService> = {
+      'importedFile': importedFileMock
+  };
+  let resolveMock = {"$stateParams": stateParamsMock};
+  let parentScopeMock: Partial<IScope> = {
+      '$resolve': resolveMock
+  };
+  let scopeMock_: Partial<IWorkspaceViewModelScope> = {
+      '$parent': parentScopeMock,
+      'current': {
+          'name': States.TYPE_WORKSPACE
+      }
+  }
+
   beforeEach(async(() => {
     TestBed.configureTestingModule({
       declarations: [ TypeWorkspaceGeneralComponent ],
       imports: [
         ReactiveFormsModule,
+        SdcUiComponentsModule,
         TranslateModule
       ],
       providers: [
+        {provide: TranslateService, useValue: translateServiceMock},
+        {provide: "$scope", useValue: scopeMock_ },
+        {provide: "$state", useValue: {}},
+        {provide: DataTypeService, useValue: dataTypeServiceMock},
+        {provide: ModelService, useValue: modelServiceMock},
         {provide: TranslateService, useValue: translateServiceMock}
       ]
     })
@@ -50,6 +81,7 @@ describe('TypeWorkspaceGeneralComponent', () => {
   beforeEach(() => {
     fixture = TestBed.createComponent(TypeWorkspaceGeneralComponent);
     component = fixture.componentInstance;
+    component.dataTypeMap$ = new Observable<Map<string, DataTypeModel>>();
     fixture.detectChanges();
   });
 
index 8728c30..a6e4d1e 100644 (file)
  *  ============LICENSE_END=========================================================
  */
 
-import {Component, Input, OnInit} from '@angular/core';
+import {Component, EventEmitter, Inject, Input, OnInit, Output} from '@angular/core';
 import {FormControl, FormGroup, Validators} from "@angular/forms";
 import {DataTypeModel} from "../../../../models/data-types";
-import { DEFAULT_MODEL_NAME } from "app/utils/constants";
+import {DEFAULT_MODEL_NAME} from "app/utils/constants";
+import {IWorkspaceViewModelScope} from "../../../../view-models/workspace/workspace-view-model";
+import {ServiceDataTypeReader} from "../../../../utils/service-data-type-reader";
+import {TranslateService} from "../../../shared/translator/translate.service";
+import {SdcUiServices} from "onap-ui-angular/dist";
+import {ModelService} from "../../../services/model.service";
+import {Model} from "../../../../models/model";
+import {DataTypesMap} from "../../../../models/data-types-map";
+import {DataTypeService} from "../../../services/data-type.service";
+import {Observable} from "rxjs/Observable";
+import {IDropDownOption} from "onap-ui-angular/dist/form-elements/dropdown/dropdown-models";
 
 @Component({
   selector: 'app-type-workspace-general',
@@ -30,9 +40,18 @@ import { DEFAULT_MODEL_NAME } from "app/utils/constants";
   styleUrls: ['./type-workspace-general.component.less']
 })
 export class TypeWorkspaceGeneralComponent implements OnInit {
+
   @Input() isViewOnly = true;
   @Input() dataType: DataTypeModel = new DataTypeModel();
-
+  @Output() onImportedType = new EventEmitter<any>();
+  importedFile: File;
+  models: Array<Model>;
+  selectedModelName: string;
+  dataTypes: DataTypesMap;
+  derivedFromName: string;
+  dataTypeMap$: Observable<Map<string, DataTypeModel>>;
+  dataTypeMap: Map<string, DataTypeModel>;
+  typeOptions: Array<IDropDownOption>;
   DEFAULT_MODEL_NAME = DEFAULT_MODEL_NAME;
 
   type: FormControl = new FormControl(undefined, [Validators.required, Validators.minLength(1), Validators.maxLength(300)]);
@@ -46,17 +65,92 @@ export class TypeWorkspaceGeneralComponent implements OnInit {
     'derivedFrom': this.derivedFrom
   });
 
+  constructor(@Inject('$scope') private $scope: IWorkspaceViewModelScope,
+              @Inject('$state') private $state: ng.ui.IStateService,
+              protected dataTypeService: DataTypeService,
+              private modalServiceSdcUI: SdcUiServices.ModalService,
+              private modelService: ModelService,
+              private translateService: TranslateService) {
+      this.typeOptions = [];
+  }
+
   ngOnInit(): void {
+      this.getImportedFile();
+      if (!this.isViewOnly) {
+          console.log("file size: " + this.importedFile.size);
+          console.log("file type: " + this.importedFile.type);
+          console.log("file lastModifiedDate: " + this.importedFile.lastModifiedDate);
+
+          new ServiceDataTypeReader().read(this.importedFile).then(
+              (serviceType) => {
+                  this.dataType = serviceType;
+                  this.dataType.modificationTime = this.importedFile.lastModifiedDate;
+                  this.dataType.creationTime = this.importedFile.lastModifiedDate;
+                  this.derivedFromName = serviceType.derivedFromName;
+                  this.dataType.uniqueId = this.dataType.model ? this.dataType.model + "." + this.dataType.name : this.dataType.name + ".datatype";
+                  this.$scope.dataType = this.dataType;
+                  this.onImportedType.emit(this.dataType);
+
+                  this.models = [];
+                  this.modelService.getDataTypeModels(this.derivedFromName).subscribe((modelsFound: any) => {
+                      modelsFound.sort().forEach(modelName => {
+                          let model:Model;
+                          if (modelName === null || "" === modelName) {
+                              model = new Model({"name": DEFAULT_MODEL_NAME, "derivedFrom": "", "modelType": "normative"});
+                          }
+                          else {
+                              model = new Model({"name": modelName, "derivedFrom": "", "modelType": "normative"});
+                          }
+                          this.models.push(model);
+                      });
+                      this.onModelChange();
+                      this.$scope.dataType = this.dataType;
+                  });
+
+              },
+              (error) => {
+                  const errorMsg = this.translateService.translate('IMPORT_DATA_TYPE_FAILURE_MESSAGE_TEXT');
+                  console.error(errorMsg, error);
+                  const errorDetails = {
+                      'Error': error.reason,
+                      'Details': error.message
+                  };
+                  console.error(error.reason);
+                  this.modalServiceSdcUI.openErrorDetailModal('Error', errorMsg,
+                      'error-modal', errorDetails);
+                  this.$state.go('dashboard');
+              });
+      }
     this.initForm();
   }
 
+  onModelChange(): void {
+    this.selectedModelName = this.models.filter(x => x.name == this.model.value).pop().name;
+    console.log("selected model: " + this.selectedModelName);
+    this.dataType.model = new Model({"name": this.selectedModelName, "derivedFrom": "", "modelType": "normative"});
+    this.dataType.uniqueId = this.dataType.model.name === DEFAULT_MODEL_NAME ?
+        this.dataType.name + ".datatype" : this.dataType.model.name + "." + this.dataType.name + ".datatype";
+    this.$scope.dataType.derivedFromName = this.derivedFromName;
+    this.$scope.dataType = this.dataType;
+    this.$scope.dataType.model = this.dataType.model;
+  }
+
+  private getImportedFile(): void {
+      let importedFile = this.$scope["$parent"]["$resolve"]["$stateParams"]["importedFile"];
+      this.importedFile = <File>importedFile;
+      this.$scope.importFile = this.importedFile;
+      if (this.importedFile) {
+          this.isViewOnly = false;
+      }
+  }
+
   private initForm(): void {
     if (!this.dataType) {
       return;
     }
     this.type.setValue(this.dataType.name);
     this.description.setValue(this.dataType.description);
-    this.model.setValue(this.dataType.model);
-    this.derivedFrom.setValue(this.dataType.derivedFrom);
+    this.model.setValue(this.dataType.model ? this.dataType.model : this.$scope.dataType && this.$scope.dataType.model ? this.$scope.dataType.model : DEFAULT_MODEL_NAME);
+    this.derivedFrom.setValue(this.dataType.derivedFromName);
   }
 }
index e6e9c12..f89be56 100644 (file)
@@ -31,6 +31,10 @@ import {DataTypeModel} from "../../../../models/data-types";
 import {Component, ViewChild} from "@angular/core";
 import {PropertyBEModel} from "../../../../models/properties-inputs/property-be-model";
 import {ModalService} from "../../../services/modal.service";
+import {IScope} from "../../../../../typings/angularjs/angular";
+import {IWorkspaceViewModelScope} from "../../../../view-models/workspace/workspace-view-model";
+import {States} from "../../../../utils/constants";
+import {SdcUiServices} from "onap-ui-angular/dist";
 
 describe('TypeWorkspacePropertiesComponent', () => {
     const messages = require("../../../../../assets/languages/en_US.json");
@@ -54,6 +58,28 @@ describe('TypeWorkspacePropertiesComponent', () => {
             return messages[translateKey];
         })
     };
+    let importedFileMock: File = null;
+    let stateParamsMock: Partial<ng.ui.IStateParamsService> = {
+        'importedFile': importedFileMock
+    };
+    let resolveMock = {"$stateParams": stateParamsMock};
+    let parentScopeMock: Partial<IScope> = {
+        '$resolve': resolveMock
+    };
+    let scopeMock_: Partial<IWorkspaceViewModelScope> = {
+        '$parent': parentScopeMock,
+        'current': {
+            'name': States.TYPE_WORKSPACE
+        }
+    }
+    let stateMock: Partial<ng.ui.IStateService> = {
+        'current': {
+            'name': States.TYPE_WORKSPACE
+        }
+    };
+
+    let modalServiceSdcUIMock: Partial<SdcUiServices.ModalService>;
+    let modalServiceMock: Partial<ModalService>;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
@@ -65,6 +91,10 @@ describe('TypeWorkspacePropertiesComponent', () => {
             providers: [
                 {provide: DataTypeService, useValue: dataTypeServiceMock},
                 {provide: TranslateService, useValue: translateServiceMock},
+                {provide: SdcUiServices.ModalService, useValue: modalServiceSdcUIMock},
+                {provide: ModalService, useValue: modalServiceMock},
+                {provide: "$scope", useValue: scopeMock_},
+                {provide: '$state', useValue: stateMock},
                 {provide: ModalService, useValue: modalService}
             ]
         })
index f53ad5b..bcc5fe9 100644 (file)
@@ -19,7 +19,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-import {Component, Input, OnInit} from '@angular/core';
+import {Component, Inject, Input, OnInit} from '@angular/core';
 import {DataTypeModel} from "../../../../models/data-types";
 import {DataTypeService} from "../../../services/data-type.service";
 import {PropertyBEModel} from "../../../../models/properties-inputs/property-be-model";
@@ -30,6 +30,8 @@ import {ModalModel} from "../../../../models/modal";
 import {ButtonModel} from "../../../../models/button";
 import {TranslateService} from "../../../shared/translator/translate.service";
 import {AddPropertyComponent, PropertyValidationEvent} from "./add-property/add-property.component";
+import {IWorkspaceViewModelScope} from "../../../../view-models/workspace/workspace-view-model";
+import {SdcUiServices} from "onap-ui-angular/dist";
 
 @Component({
     selector: 'app-type-workspace-properties',
@@ -40,6 +42,8 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
     @Input() isViewOnly = true;
     @Input() dataType: DataTypeModel = new DataTypeModel();
 
+    importedFile: File;
+    derivedFromName: string;
     properties: Array<PropertyBEModel> = [];
     filteredProperties: Array<PropertyBEModel> = [];
     tableHeadersList: Array<TableHeader> = [];
@@ -48,7 +52,12 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
     tableFilterTerm: string = undefined;
     tableSearchTermUpdate = new Subject<string>();
 
-    constructor(private dataTypeService: DataTypeService, private modalService: ModalService, private translateService: TranslateService) {
+    constructor(@Inject('$scope') private $scope: IWorkspaceViewModelScope,
+                @Inject('$state') private $state: ng.ui.IStateService,
+                protected dataTypeService: DataTypeService,
+                private modalServiceSdcUI: SdcUiServices.ModalService,
+                private modalService: ModalService,
+                private translateService: TranslateService) {
     }
 
     ngOnInit(): void {
@@ -70,22 +79,12 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
             {title: 'Required', property: 'required'},
             {title: 'Description', property: 'description'},
         ];
-
         this.tableSortBy = this.tableHeadersList[0].property;
     }
 
     private initProperties(): void {
         this.dataTypeService.findAllProperties(this.dataType.uniqueId).subscribe(properties => {
-            this.properties = properties.map(value => {
-                const property = new PropertyBEModel(value);
-                if (property.defaultValue) {
-                    property.defaultValue = JSON.parse(property.defaultValue);
-                }
-
-                return property;
-            });
-            this.filteredProperties = Array.from(this.properties);
-            this.sort();
+            this.showPropertiesMap(properties);
         });
     }
 
@@ -188,6 +187,19 @@ export class TypeWorkspacePropertiesComponent implements OnInit {
     onRowClick(property: PropertyBEModel) {
         this.openAddPropertyModal(property, true);
     }
+
+    private showPropertiesMap(properties: Array<PropertyBEModel>): void {
+        this.properties = properties.map(value => {
+            const property = new PropertyBEModel(value);
+            if (property.defaultValue) {
+                property.defaultValue = JSON.parse(property.defaultValue);
+            }
+
+            return property;
+        });
+        this.filteredProperties = Array.from(this.properties);
+        this.sort();
+    }
 }
 
 interface TableHeader {
index 105c89d..cdd8e41 100644 (file)
@@ -32,6 +32,8 @@
         <div class="progress-container">
         </div>
         <div class="sdc-workspace-top-bar-buttons">
+          <button *ngIf="!isViewOnly" data-ng-disabled="!isValidForm || isDisableMode() || isLoading || unsavedChanges" (click)="createImportType()" class="tlv-btn outline green" data-tests-id="create/save">Create</button>
+          <span *ngIf="!isViewOnly" class="delimiter"></span>
           <span class="sprite-new x-btn" (click)="goToBreadcrumbHome()" sdc-smart-tooltip="Close" [title]="'CLOSE_LABEL' | translate"></span>
         </div>
       </div>
@@ -43,7 +45,7 @@
           </div>
         </div>
         <div class="w-sdc-main-container-body-content" *ngIf="dataType">
-          <app-type-workspace-general *ngIf="currentMenu.state === 'general'" [dataType]="dataType"></app-type-workspace-general>
+          <app-type-workspace-general *ngIf="currentMenu.state === 'general'" [dataType]="dataType" (onImportedType)="onImportedType($event)"></app-type-workspace-general>
           <app-type-workspace-properties *ngIf="currentMenu.state === 'properties'" [dataType]="dataType" [isViewOnly]="dataType.normative"></app-type-workspace-properties>
           <app-type-workspace-tosca-artifact *ngIf="currentMenu.state === 'tosca_artifacts'" [dataType]="dataType"></app-type-workspace-tosca-artifact>
         </div>
index 3db2504..a5cf20f 100644 (file)
@@ -41,6 +41,10 @@ import {TypeWorkspacePropertiesComponent} from "./type-workspace-properties/type
 import {TypeWorkspaceToscaArtifactPageComponent} from "./type-workspace-tosca-artifacts/type-workspace-tosca-artifact-page.component";
 import {NgxDatatableModule} from "@swimlane/ngx-datatable";
 import {SvgIconModule} from "onap-ui-angular/dist/svg-icon/svg-icon.module";
+import {NO_ERRORS_SCHEMA} from "@angular/core";
+import {IScope} from "angular";
+import {IWorkspaceViewModelScope} from "../../../view-models/workspace/workspace-view-model";
+import {ModelService} from "../../services/model.service";
 
 describe('TypeWorkspaceComponent', () => {
   let component: TypeWorkspaceComponent;
@@ -50,6 +54,7 @@ describe('TypeWorkspaceComponent', () => {
     'translate': jest.fn()
   };
   let dataTypeServiceMock: Partial<DataTypeService>;
+  let notificationMock: Partial<any>;
   let cacheService: Partial<CacheService> = {
     'get': jest.fn(param => {
       if (param === 'version') {
@@ -65,7 +70,7 @@ describe('TypeWorkspaceComponent', () => {
       'name': States.TYPE_WORKSPACE
     }
   };
-  let stateParamsMock: Partial<ng.ui.IStateParamsService> = {};
+
   let injectorMock: Partial<ng.auto.IInjectorService> = {
     'get': jest.fn(param => {
       if (param === '$state') {
@@ -89,6 +94,21 @@ describe('TypeWorkspaceComponent', () => {
       return user;
     })
   };
+  let importedFileMock: File = null;
+  let stateParamsMock: Partial<ng.ui.IStateParamsService> = {
+      'importedFile': importedFileMock
+  };
+  let resolveMock = {"$stateParams": stateParamsMock};
+  let parentScopeMock: Partial<IScope> = {
+      '$resolve': resolveMock
+  };
+  let scopeMock_: Partial<IWorkspaceViewModelScope> = {
+      '$parent': parentScopeMock,
+      'current': {
+          'name': States.TYPE_WORKSPACE
+      }
+  }
+  let modelServiceMock: Partial<ModelService>;
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
@@ -102,10 +122,14 @@ describe('TypeWorkspaceComponent', () => {
         NgxDatatableModule,
         SvgIconModule
       ],
+      schemas: [NO_ERRORS_SCHEMA],
       providers: [
         {provide: DataTypeService, useValue: dataTypeServiceMock},
         {provide: TranslateService, useValue: translateServiceMock},
         {provide: CacheService, useValue: cacheService},
+        {provide: "Notification", useValue: notificationMock },
+        {provide: ModelService, useValue: modelServiceMock},
+        {provide: "$scope", useValue: scopeMock_ },
         {provide: '$state', useValue: stateMock},
         {provide: '$stateParams', useValue: stateParamsMock},
         {provide: '$injector', useValue: injectorMock},
index d0d5ae8..e0f7ac7 100644 (file)
  *  ============LICENSE_END=========================================================
  */
 
-import {Component, Inject, OnInit} from '@angular/core';
+import {Component, Inject, Injector, OnInit} from '@angular/core';
 import {MenuItem, MenuItemGroup} from "../../../utils/menu-handler";
 import {CacheService} from "../../services/cache.service";
 import {DataTypeModel} from "../../../models/data-types";
 import {DataTypeService} from "../../services/data-type.service";
+import {IWorkspaceViewModelScope} from "../../../view-models/workspace/workspace-view-model";
+import {TranslateService} from "../../shared/translator/translate.service";
+import {HttpErrorResponse} from "@angular/common/http";
+import {ServerErrorResponse} from "../../../models/server-error-response";
+import {Observable} from "rxjs/Observable";
+import {SdcUiServices} from "onap-ui-angular/dist";
 
 @Component({
   selector: 'app-type-workspace',
@@ -33,27 +39,32 @@ import {DataTypeService} from "../../services/data-type.service";
 export class TypeWorkspaceComponent implements OnInit {
 
   private typeMenuItemGroup: MenuItemGroup;
-
   isLoading: boolean;
   disabled: boolean;
   isViewOnly: boolean = true;
   sdcVersion: string;
   breadcrumbsModel: Array<MenuItemGroup> = [];
   dataType: DataTypeModel = new DataTypeModel();
+  importedDataType: DataTypeModel = new DataTypeModel();
   currentMenu: MenuItem;
 
-  constructor(private dataTypeService: DataTypeService, private cacheService: CacheService,
+  constructor(@Inject('$scope') private $scope: IWorkspaceViewModelScope,
+              private dataTypeService: DataTypeService, private cacheService: CacheService,
+              @Inject('Notification') private Notification: any,
+              private translateService: TranslateService,
               @Inject('$state') private $state: ng.ui.IStateService,
-              @Inject('$stateParams') private stateParams) { }
+              @Inject('$stateParams') private stateParams,
+              private injector: Injector) { }
 
   ngOnInit(): void {
     this.sdcVersion = this.cacheService.get('version');
     this.typeMenuItemGroup = this.createTypeBreadcrumb();
+
     this.loadDataType();
   }
 
   private loadDataType(): void {
-    if (this.stateParams.id) {
+    if (this.stateParams.id && this.stateParams.id != "import") {
       this.dataTypeService.findById(this.stateParams.id).subscribe(dataType => {
         this.dataType = dataType;
         this.updateTypeBreadcrumb();
@@ -61,11 +72,49 @@ export class TypeWorkspaceComponent implements OnInit {
         console.debug('Could not find data type %s', this.stateParams.id, error);
         this.goToBreadcrumbHome();
       });
+        this.isViewOnly = true;
     } else {
+
+      this.isViewOnly = false;
       this.dataType = new DataTypeModel();
     }
   }
 
+  onImportedType(dataType) {
+    this.typeMenuItemGroup.updateSelectedMenuItemText(`Data Type: ${dataType.name}`);
+  }
+
+  private createImportType() {
+      if (this.$scope.dataType.derivedFromName != undefined && this.$scope.dataType.model != undefined) {
+          this.dataTypeService.createImportedType(this.$scope.dataType.model.name, this.$scope.importFile)
+              .subscribe(response => {
+              this.importedDataType = new DataTypeModel(response);
+              this.Notification.success({
+                  message: this.$scope.dataType.name + ' ' + this.translateService.translate('IMPORT_DATA_TYPE_SUCCESS_MESSAGE_TEXT'),
+                  title: this.translateService.translate('IMPORT_DATA_TYPE_TITLE_TEXT')
+              });
+              this.$state.go(this.$state.current.name, {importedFile: null, id: this.$scope.dataType.uniqueId, isViewOnly: true}, {reload: true});
+          }, error => {//because overriding http interceptor
+                  if (error instanceof HttpErrorResponse) {
+                      const errorResponse: ServerErrorResponse = new ServerErrorResponse(error);
+                      const modalService = this.injector.get(SdcUiServices.ModalService);
+                      const errorDetails = {
+                          'Error Code': errorResponse.status != 409 ? errorResponse.messageId : "Data Type already exists",
+                          'Status Code': errorResponse.status
+                      };
+                      modalService.openErrorDetailModal('Error', errorResponse.status != 409 ? errorResponse.message : "Data Type already exists", 'error-modal', errorDetails);
+                      return Observable.throwError(error);
+                  }
+              });
+      }
+      else {
+          this.Notification.error({
+              message: this.$scope.dataType.name + ' ' + "Derived from is invalid in file",
+              title: this.translateService.translate('IMPORT_DATA_TYPE_TITLE_TEXT')
+          });
+      }
+  }
+
   private updateTypeBreadcrumb(): void {
     this.typeMenuItemGroup.updateSelectedMenuItemText(`Data Type: ${this.dataType.name}`);
   }
@@ -82,10 +131,15 @@ export class TypeWorkspaceComponent implements OnInit {
 
   onMenuUpdate(menuItemGroup: MenuItemGroup): void {
     this.breadcrumbsModel.push(...[this.typeMenuItemGroup, menuItemGroup]);
+    if (!this.isViewOnly) {
+        this.$scope.leftBarTabs.menuItems.forEach((item: MenuItem) => {
+            item.isDisabled = ('general' !== item.state);
+            item.disabledCategory = ('general' !== item.state);
+        });
+    }
   }
 
   onMenuClick(menuItem: MenuItem): void {
     this.currentMenu = menuItem;
   }
-
 }
index 87b29b6..e7ddb46 100644 (file)
@@ -38,6 +38,7 @@ import {TypeWorkspaceToscaArtifactPageComponent} from "./type-workspace-tosca-ar
 import {ModalService} from "../../services/modal.service";
 import {AddPropertyComponent} from './type-workspace-properties/add-property/add-property.component';
 import {InterfaceOperationHandlerModule} from "../composition/interface-operatons/operation-creator/interface-operation-handler.module";
+import {AutoCompleteModule} from "onap-ui-angular/dist/autocomplete/autocomplete.module";
 
 @NgModule({
    imports: [
@@ -51,6 +52,7 @@ import {InterfaceOperationHandlerModule} from "../composition/interface-operaton
         InterfaceOperationHandlerModule,
         NgxDatatableModule,
         SvgIconModule,
+        AutoCompleteModule
     ],
     declarations: [
         TypeWorkspaceComponent,
@@ -64,7 +66,8 @@ import {InterfaceOperationHandlerModule} from "../composition/interface-operaton
         CacheService,
         WorkspaceMenuComponent,
         DataTypeService,
-        ModalService
+        ModalService,
+        FileReader
     ],
     entryComponents: [TypeWorkspaceComponent, AddPropertyComponent],
     exports: [TypeWorkspaceComponent]
index 18c6949..02d2683 100644 (file)
@@ -25,8 +25,8 @@
   </div>
   <div class="i-sdc-designer-sidebar-section-content-item"
        [ngClass]="{'selected': isSelected(menuItem)}"
-       *ngFor="let menuItem of leftBarTabs.menuItems">
-    <div class="expand-collapse-menu-box-item-text" ng-class="{'disabled': menuItem.isDisabled }">
+       *ngFor="let menuItem of $scope.leftBarTabs.menuItems">
+    <div class="expand-collapse-menu-box-item-text" [ngClass]="{ 'disabled': menuItem.isDisabled }">
       <button [attr.data-tests-id]="menuItem.text + 'LeftSideMenu'" type="button" class="i-sdc-designer-sidebar-section-content-item-service-cat"
               (click)="menuItem.callback()" [disabled]="menuItem.disabledCategory">{{menuItem.text}}</button>
     </div>
index a91258c..d9bb883 100644 (file)
@@ -26,6 +26,8 @@ import {CacheService} from "../../../services/cache.service";
 import {States} from "../../../../utils/constants";
 import {IAppMenu} from "../../../../models/app-config";
 import {SdcMenuToken} from "../../../config/sdc-menu.config";
+import {IScope} from "../../../../../typings/angularjs/angular";
+import {IWorkspaceViewModelScope} from "../../../../view-models/workspace/workspace-view-model";
 
 describe('WorkspaceMenuComponent', () => {
   let component: WorkspaceMenuComponent;
@@ -59,6 +61,20 @@ describe('WorkspaceMenuComponent', () => {
       }
     })
   };
+  let importedFileMock: File = null;
+  let stateParamsMock: Partial<ng.ui.IStateParamsService> = {
+      'importedFile': importedFileMock
+  };
+  let resolveMock = {"$stateParams": stateParamsMock};
+  let parentScopeMock: Partial<IScope> = {
+      '$resolve': resolveMock
+  };
+  let scopeMock_: Partial<IWorkspaceViewModelScope> = {
+      '$parent': parentScopeMock,
+      'current': {
+          'name': States.TYPE_WORKSPACE
+      }
+  }
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
@@ -66,6 +82,7 @@ describe('WorkspaceMenuComponent', () => {
       providers: [
         {provide: CacheService, useValue: cacheService},
         {provide: '$injector', useValue: injectorMock},
+        {provide: "$scope", useValue: scopeMock_ },
         {provide: SdcMenuToken, useValue: sdcMenuMock}
       ]
     })
index c5e49d4..ee4f3a8 100644 (file)
@@ -25,6 +25,7 @@ import {CacheService} from "../../../services/cache.service";
 import {IAppMenu} from "../../../../models/app-config";
 import {IUserProperties} from "../../../../models/user";
 import {SdcMenuToken} from "../../../config/sdc-menu.config";
+import {IWorkspaceViewModelScope} from "../../../../view-models/workspace/workspace-view-model";
 
 @Component({
     selector: 'app-workspace-menu',
@@ -44,7 +45,10 @@ export class WorkspaceMenuComponent implements OnInit {
 
     leftBarTabs: MenuItemGroup = new MenuItemGroup();
 
-    constructor(private cacheService: CacheService, @Inject(SdcMenuToken) private sdcMenu: IAppMenu, @Inject('$injector') $injector) {
+    constructor(private cacheService: CacheService,
+                @Inject('$scope') private $scope: IWorkspaceViewModelScope,
+                @Inject(SdcMenuToken) private sdcMenu: IAppMenu,
+                @Inject('$injector') $injector) {
         this.$state = $injector.get('$state');
         this.$q = $injector.get('$q');
     }
@@ -70,6 +74,7 @@ export class WorkspaceMenuComponent implements OnInit {
             return menuItem;
         });
         this.updateSelectedMenuItem();
+        this.$scope.leftBarTabs = this.leftBarTabs;
         this.onMenuUpdate.emit(this.leftBarTabs);
     }
 
index 298ba90..3a27801 100644 (file)
@@ -26,8 +26,9 @@ import { PROPERTY_DATA } from "app/utils";
 import {DerivedFEAttribute} from "../../models/attributes-outputs/derived-fe-attribute";
 import {ISdcConfig} from "../config/sdc-config.config.factory";
 import {SdcConfigToken} from "../config/sdc-config.config";
-import {HttpClient} from "@angular/common/http";
+import {HttpBackend, HttpClient, HttpHeaders} from "@angular/common/http";
 import {Observable} from "rxjs/Observable";
+import {AuthenticationService} from "./authentication.service";
 
 /** This is a new service for NG2, to eventually replace app/services/data-types-service.ts
  *
@@ -40,14 +41,17 @@ export class DataTypeService {
     public dataTypes: DataTypesMap;
     private readonly baseUrl: string;
     private readonly dataTypeUrl: string;
+    private readonly dataTypeUploadUrl: string;
 
-    constructor(private dataTypeService: DataTypesService, private httpClient: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) {
+
+    constructor(private dataTypeService: DataTypesService, private authService: AuthenticationService, private handler: HttpBackend, private httpClient: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) {
         this.dataTypes = dataTypeService.getAllDataTypes(); //This should eventually be replaced by an NG2 call to the backend instead of utilizing Angular1 downgraded component.
         this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root;
-        this.dataTypeUrl = `${this.baseUrl}data-types`
+        this.dataTypeUrl = `${this.baseUrl}data-types`;
+        this.dataTypeUploadUrl = `${this.baseUrl}uploadType`;
+        this.httpClient = new HttpClient(handler);
     }
 
-
     public getDataTypeByModelAndTypeName(modelName: string, typeName: string): DataTypeModel {
         this.dataTypes = this.dataTypeService.getAllDataTypesFromModel(modelName);
         let dataTypeFound = this.dataTypes[typeName];
@@ -88,6 +92,18 @@ export class DataTypeService {
         return this.httpClient.post<PropertyBEModel>(url, property);
     }
 
+    public createImportedType(model: string, importingFile: File): Observable<any> {
+        const url = `${this.dataTypeUploadUrl}/datatypesyaml`;
+        const formData = new FormData();
+        formData.append('dataTypesYaml', importingFile);
+        formData.append('model', model != 'SDC AID' ? model : "")
+        formData.append('includeToModelImport', "true");
+        let headers = new HttpHeaders({'USER_ID': this.authService.getLoggedinUser().userId});
+        let options = {headers: headers};
+
+        return this.httpClient.post<any>(url, formData, options);
+    }
+
     public getConstraintsByParentTypeAndUniqueID(rootPropertyType, propertyName){
         // const property = this.dataTypes[rootPropertyType].properties.filter(property =>
         //     property.name == propertyName);
@@ -149,6 +165,5 @@ export class DataTypeService {
             if (prop.name == 'instance_name') prop.hidden = generatedNamingVal;
         });
     }
-
 }
 
index 83e16b1..60b7ac9 100644 (file)
@@ -39,4 +39,7 @@ export class ModelService {
     return this.http.get<Model[]>(this.baseUrl + "/v1/catalog/model?modelType=" + type);
   }
 
+  getDataTypeModels(typeName: string):Observable<any> {
+    return this.http.get<Array<string>>(this.baseUrl + "/v1/catalog/data-types/" + typeName + "/models");
+  }
 }
index 09ece87..5d247a1 100644 (file)
@@ -134,6 +134,21 @@ export class DataTypesService implements IDataTypesService {
         });
     }
 
+    public findAllDataTypesByModelIncludingRoot = (modelName: string): Promise<Map<string, DataTypeModel>> => {
+        return new Promise<Map<string, DataTypeModel>>((resolve, reject) => {
+            this.fetchDataTypesByModel(modelName).then(response => {
+                const dataTypes = response.data;
+                const dataTypeMap = new Map<string, DataTypeModel>();
+                for(const dataTypeKey of Object.keys(dataTypes)) {
+                    dataTypeMap.set(dataTypeKey, new DataTypeModel(dataTypes[dataTypeKey]))
+                }
+                resolve(dataTypeMap);
+            }).catch(reason => {
+                reject(reason);
+            });
+        });
+    }
+
     public getAllDataTypes = ():DataTypesMap => {
         return this.dataTypes;
     };
diff --git a/catalog-ui/src/app/utils/service-data-type-reader.ts b/catalog-ui/src/app/utils/service-data-type-reader.ts
new file mode 100644 (file)
index 0000000..fd5ee2f
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2022 Nordix Foundation. 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 {DataTypeModel, PropertyBEModel} from "../models";
+import {load} from 'js-yaml';
+
+export class ServiceDataTypeReader {
+
+    private serviceDataType = new DataTypeModel();
+
+    public read(dataTypeFile: File): Promise<DataTypeModel> {
+
+        return new Promise<DataTypeModel>((resolve, reject) => {
+            const reader = new FileReader();
+            reader.onloadend = () => {
+                try {
+                    const result = <String>reader.result;
+                    const loadedContent = load(result);
+                    console.log("Readed content: " + loadedContent);
+                    this.readName(loadedContent);
+                    this.readDerivedFrom(this.getDataType(loadedContent));
+                    this.readDescription(this.getDataType(loadedContent));
+                    this.readProperties(this.getDataType(loadedContent));
+                    resolve(this.serviceDataType);
+                } catch (error) {
+                    reject(error);
+                }
+            }
+            reader.readAsText(dataTypeFile);
+        });
+    }
+
+    private getDataType(fileContent:any) {
+        const index = Object.keys(fileContent).indexOf("data_types",0)
+        if (index == -1){
+            return fileContent;
+        }
+        return fileContent["data_types"];
+    }
+
+    private readName(fileContent: any) {
+        this.serviceDataType.name = Object.keys(fileContent).values().next().value;
+    }
+
+    private readDerivedFrom(fileContent: any) {
+        let dataType = Object.keys(fileContent).values().next().value;
+        this.serviceDataType.derivedFromName = fileContent[dataType]["derived_from"];
+    }
+
+    private readDescription(fileContent: any) {
+        let dataType = Object.keys(fileContent).values().next().value;
+        this.serviceDataType.description = fileContent[dataType]["description"];
+    }
+
+    private readProperties(fileContent: any) {
+        this.serviceDataType.properties = new Array<PropertyBEModel>();
+        let dataType = Object.keys(fileContent).values().next().value;
+        const properties = fileContent[dataType]["properties"];
+        Object.keys(properties).forEach((key )=>
+            {
+                let property = new PropertyBEModel();
+                property.name = key;
+                property.description = properties[key]["description"];
+                property.type = properties[key]["type"];
+                property.schemaType = properties[key]["schema"];
+                property.required = properties[key]["required"];
+                this.serviceDataType.properties.push(property);
+            }
+        );
+    }
+}
\ No newline at end of file
index 0778b2d..accc043 100644 (file)
@@ -23,7 +23,7 @@
  */
 'use strict';
 import * as _ from 'lodash';
-import {Component, IAppMenu, IUserProperties, Plugin, PluginsConfiguration, Resource, Service} from 'app/models';
+import {Component, DataTypeModel, IAppMenu, IUserProperties, Plugin, PluginsConfiguration, Resource, Service} from 'app/models';
 import {
     CHANGE_COMPONENT_CSAR_VERSION_FLAG,
     ChangeLifecycleStateHandler,
@@ -57,6 +57,7 @@ export interface IWorkspaceViewModelScope extends ng.IScope {
     isLoading: boolean;
     isCreateProgress: boolean;
     component: Component;
+    dataType: DataTypeModel;
     originComponent: Component;
     componentType: string;
     importFile: any;
index 5dec922..bb2ebd5 100644 (file)
   "UNIQUE_ID_LABEL": "Unique Id",
   "=========== SERVICE IMPORT ===========": "",
   "IMPORT_FAILURE_MESSAGE_TEXT": "Import Failure - error reading CSAR",
+  "=========== DATA TYPE IMPORT ===========": "",
+  "IMPORT_DATA_TYPE_FAILURE_MESSAGE_TEXT": "Import Failure - error reading data type file",
+  "IMPORT_DATA_TYPE_FAILURE_PROCESSING_MESSAGE_TEXT": "Import Failure - error importing data type",
+  "IMPORT_DATA_TYPE_SUCCESS_MESSAGE_TEXT": "Successfully imported",
+  "IMPORT_DATA_TYPE_TITLE_TEXT": "Import Data Type",
   "=========== PROPERTIES ===========": "",
   "PROPERTY_LIST_EMPTY_MESSAGE": "There are no properties to display",
   "PROPERTY_SHOWING_LABEL": "Showing Properties",