Merge "Refactor, fix code formatting and add unittests"
authorRemigiusz Janeczek <remigiusz.janeczek@nokia.com>
Fri, 25 Sep 2020 08:34:53 +0000 (08:34 +0000)
committerGerrit Code Review <gerrit@onap.org>
Fri, 25 Sep 2020 08:34:53 +0000 (08:34 +0000)
318 files changed:
mod/bpgenerator/TestCases/imports/imports.yaml [new file with mode: 0644]
mod/bpgenerator/TestCases/imports/importsWithBlanks.yaml [new file with mode: 0644]
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/core/TestComponentSpec.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/Appconfig.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/Blueprint.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/Imports.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/Interfaces.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/Properties.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/ResourceConfig.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/Start.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/StartInputs.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/dmaap/DmaapInfo.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/blueprint/dmaap/DmaapObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Artifacts.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Auxilary.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/CallsObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/ConstraintsObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Container.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/EntrySchemaObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/HealthCheck.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Host.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Parameters.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Policy.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/PolicySchemaObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/ProvidesObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Publishes.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/ReconfigsObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/RequestResponseObj.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Self.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Services.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Streams.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Subscribes.java
mod/bpgenerator/src/main/java/org/onap/blueprintgenerator/models/componentspec/Volumes.java
mod/bpgenerator/src/test/java/org/onap/blueprintgenerator/core/BlueprintGeneratorTest.java
mod/bpgenerator/src/test/java/org/onap/blueprintgenerator/models/blueprint/ImportsTest.java [new file with mode: 0644]
mod/bpgenerator/src/test/java/org/onap/blueprintgenerator/models/blueprint/tls/ExternalCertificateParametersFactoryTest.java
mod/runtimeapi/pom.xml
mod/runtimeapi/runtime-core/pom.xml
mod/runtimeapi/runtime-web/pom.xml
mod2/auth-service/Dockerfile [new file with mode: 0644]
mod2/auth-service/pom.xml [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/AuthServiceApplication.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/DataLoader.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/AuthController.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/RoleController.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/UserController.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/AppExceptionHandler.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/IllegalUserOperationException.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/RoleNotExistsException.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/UserAlreadyExistsException.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/UserNotFoundException.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/GenericResponse.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/JwtResponse.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/LoginRequest.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/ModUser.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/Role.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/SignupRequest.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/UpdateUserRequest.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/repositories/RoleRepository.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/repositories/UserRepository.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/WebSecurityConfigurer.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthEntryPointJwt.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthTokenFilter.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/JwtUtils.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsImpl.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsServiceImpl.java [new file with mode: 0644]
mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/services/MODUserDetailService.java [new file with mode: 0644]
mod2/auth-service/src/main/resources/application.properties [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/AuthObjectMother.java [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/RoleObjectMother.java [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/UserObjectMother.java [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/util/TestUtil.java [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/AuthControllerTest.java [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/RoleControllerTest.java [new file with mode: 0644]
mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/UserControllerTest.java [new file with mode: 0644]
mod2/auth-service/src/test/resources/application.properties [new file with mode: 0644]
mod2/auth-service/src/test/resources/http/requests/AuthLoginRequest.json [new file with mode: 0644]
mod2/auth-service/src/test/resources/http/requests/AuthSignupRequest.json [new file with mode: 0644]
mod2/auth-service/src/test/resources/specification/componentSpec_hello_world-with-dmaap.json [new file with mode: 0644]
mod2/auth-service/src/test/resources/specification/policy_json_sample_3.json [new file with mode: 0644]
mod2/catalog-service/Dockerfile [new file with mode: 0644]
mod2/catalog-service/lombok.config [new file with mode: 0644]
mod2/catalog-service/pom.xml [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/ModCatalogApplication.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mock/MockDeploymentArtifactGenerator.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mock/MockSpecificationValidationStratergy.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMicroservice.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsLocation.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsStatus.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsType.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/common/AuditFields.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifact.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactFilter.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactSearch.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactStatus.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/MsInstanceInfo.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ErrorMessages.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/MissingRequestBodyException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/OperationNotAllowedException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ReleaseNotSupportedException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ResourceConflictException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceNotFoundException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceTagAlreadyExists.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceTagInvalid.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMsServiceNameAlreadyExists.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMsServiceNameInvalid.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/common/UserNotPassedException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/BlueprintFileNameCreateException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/DeploymentArtifactNotFound.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/StatusChangeNotValidException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/msinstance/MsInstanceAlreadyExistsException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/msinstance/MsInstanceNotFoundException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/specification/SpecificationInvalid.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/specification/SpecificationNotFoundException.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/DeploymentArtifactsRef.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/MsInstance.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/MsInstanceStatus.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/DeploymentArtifactPatchRequest.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/ErrorResponse.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/GenericErrorResponse.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MicroserviceCreateRequest.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MicroserviceUpdateRequest.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MsInstanceRequest.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MsInstanceUpdateRequest.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/SpecificationRequest.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/SuccessResponse.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/DeploymentType.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/Specification.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/SpecificationStatus.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/basemicroservice/BaseMicroserviceMongoGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/basemicroservice/BaseMicroserviceMongoRepo.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/deploymentartifact/DeploymentArtifactMongoGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/deploymentartifact/DeploymentArtifactMongoRepo.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/microserviceinstance/MsInstanceMongoGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/microserviceinstance/MsInstanceMongoRepo.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/specification/SpecificationMongoGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/specification/SpecificationMongoRepo.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/AppExceptionHandler.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/BaseMicroserviceController.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/DeploymentArtifactController.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/MicroserviceInstanceController.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/SpecificationController.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/BaseMicroserviceGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/MsService.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/MsServiceImpl.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/ArtifactFileNameCreator.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactGeneratorStrategy.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactService.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactServiceImpl.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactStatusChangeHandler.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceService.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceServiceImpl.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceStatusChangeHandler.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationGateway.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationService.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationServiceImpl.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationValidationStratergy.java [new file with mode: 0644]
mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationValidatorService.java [new file with mode: 0644]
mod2/catalog-service/src/main/resources/application.properties [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/BaseMsObjectMother.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/DeploymentArtifactObjectMother.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/MsInstanceObjectMother.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/SpecificationObjectMother.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/persistence/DeploymentArtifactGatewayTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/util/TestUtil.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/BaseMicroserviceControllerTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/DeploymentArtifactControllerTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/MicroserviceInstanceControllerTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/MsRequestValidationTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/SpecificationControllerTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsInstanceServiceImplTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsInstanceStatusChangeHandlerTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsServiceImplTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/SpecificationServiceTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/ArtifactFileNameCreatorTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactServiceImplTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactStatusChangeHandlerTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/SearchDeploymentArtifactsTest.java [new file with mode: 0644]
mod2/catalog-service/src/test/resources/application.properties [new file with mode: 0644]
mod2/catalog-service/src/test/resources/http/requests/CreateSpecificationRequest.json [new file with mode: 0644]
mod2/catalog-service/src/test/resources/http/requests/CreateSpecificationResponse.json [new file with mode: 0644]
mod2/catalog-service/src/test/resources/specification/componentSpec_hello_world-with-dmaap.json [new file with mode: 0644]
mod2/catalog-service/src/test/resources/specification/policy_json_sample_3.json [new file with mode: 0644]
mod2/ui/.editorconfig [new file with mode: 0644]
mod2/ui/.gitignore [new file with mode: 0644]
mod2/ui/Dockerfile [new file with mode: 0644]
mod2/ui/Jenkinsfile [new file with mode: 0644]
mod2/ui/README.md [new file with mode: 0644]
mod2/ui/angular.json [new file with mode: 0644]
mod2/ui/browserslist [new file with mode: 0644]
mod2/ui/dependenciesFile [new file with mode: 0644]
mod2/ui/e2e/protractor.conf.js [new file with mode: 0644]
mod2/ui/e2e/src/app.e2e-spec.ts [new file with mode: 0644]
mod2/ui/e2e/src/app.po.ts [new file with mode: 0644]
mod2/ui/e2e/tsconfig.json [new file with mode: 0644]
mod2/ui/extra-webpack.config.js [new file with mode: 0644]
mod2/ui/healthcheck.sh [new file with mode: 0644]
mod2/ui/karma.conf.js [new file with mode: 0644]
mod2/ui/package-lock.json [new file with mode: 0644]
mod2/ui/package.json [new file with mode: 0644]
mod2/ui/pipelineConfig.json [new file with mode: 0644]
mod2/ui/pom.xml [new file with mode: 0644]
mod2/ui/proxy.conf.json [new file with mode: 0644]
mod2/ui/src/app/app-routing.module.ts [new file with mode: 0644]
mod2/ui/src/app/app.component.css [new file with mode: 0644]
mod2/ui/src/app/app.component.html [new file with mode: 0644]
mod2/ui/src/app/app.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/app.component.ts [new file with mode: 0644]
mod2/ui/src/app/app.module.ts [new file with mode: 0644]
mod2/ui/src/app/blueprints/blueprints.component.css [new file with mode: 0644]
mod2/ui/src/app/blueprints/blueprints.component.html [new file with mode: 0644]
mod2/ui/src/app/blueprints/blueprints.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/blueprints/blueprints.component.ts [new file with mode: 0644]
mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css [new file with mode: 0644]
mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html [new file with mode: 0644]
mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts [new file with mode: 0644]
mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css [new file with mode: 0644]
mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html [new file with mode: 0644]
mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts [new file with mode: 0644]
mod2/ui/src/app/comp-specs/comp-specs.component.css [new file with mode: 0644]
mod2/ui/src/app/comp-specs/comp-specs.component.html [new file with mode: 0644]
mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/comp-specs/comp-specs.component.ts [new file with mode: 0644]
mod2/ui/src/app/guards/auth.guard.spec.ts [new file with mode: 0644]
mod2/ui/src/app/guards/auth.guard.ts [new file with mode: 0644]
mod2/ui/src/app/guards/login.guard.spec.ts [new file with mode: 0644]
mod2/ui/src/app/guards/login.guard.ts [new file with mode: 0644]
mod2/ui/src/app/guards/role.guard.spec.ts [new file with mode: 0644]
mod2/ui/src/app/guards/role.guard.ts [new file with mode: 0644]
mod2/ui/src/app/home/home.component.css [new file with mode: 0644]
mod2/ui/src/app/home/home.component.html [new file with mode: 0644]
mod2/ui/src/app/home/home.component.ts [new file with mode: 0644]
mod2/ui/src/app/login/login.component.css [new file with mode: 0644]
mod2/ui/src/app/login/login.component.html [new file with mode: 0644]
mod2/ui/src/app/login/login.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/login/login.component.ts [new file with mode: 0644]
mod2/ui/src/app/material-elevation.directive.ts [new file with mode: 0644]
mod2/ui/src/app/microservices/microservices.component.css [new file with mode: 0644]
mod2/ui/src/app/microservices/microservices.component.html [new file with mode: 0644]
mod2/ui/src/app/microservices/microservices.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/microservices/microservices.component.ts [new file with mode: 0644]
mod2/ui/src/app/models/AuthResponse.ts [new file with mode: 0644]
mod2/ui/src/app/models/Authority.enum.ts [new file with mode: 0644]
mod2/ui/src/app/models/User.ts [new file with mode: 0644]
mod2/ui/src/app/ms-add-change/ms-add-change.component.css [new file with mode: 0644]
mod2/ui/src/app/ms-add-change/ms-add-change.component.html [new file with mode: 0644]
mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/ms-add-change/ms-add-change.component.ts [new file with mode: 0644]
mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css [new file with mode: 0644]
mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html [new file with mode: 0644]
mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts [new file with mode: 0644]
mod2/ui/src/app/msInstances/msInstances.component.css [new file with mode: 0644]
mod2/ui/src/app/msInstances/msInstances.component.html [new file with mode: 0644]
mod2/ui/src/app/msInstances/msInstances.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/msInstances/msInstances.component.ts [new file with mode: 0644]
mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css [new file with mode: 0644]
mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html [new file with mode: 0644]
mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts [new file with mode: 0644]
mod2/ui/src/app/register/register.component.css [new file with mode: 0644]
mod2/ui/src/app/register/register.component.html [new file with mode: 0644]
mod2/ui/src/app/register/register.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/register/register.component.ts [new file with mode: 0644]
mod2/ui/src/app/reset-password/reset-password.component.css [new file with mode: 0644]
mod2/ui/src/app/reset-password/reset-password.component.html [new file with mode: 0644]
mod2/ui/src/app/reset-password/reset-password.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/reset-password/reset-password.component.ts [new file with mode: 0644]
mod2/ui/src/app/services/auth.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/auth.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/base-microservice.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/base-microservice.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/breadcrumb.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/comp-spec-add.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/comp-spec-add.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/comp-specs-service.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/comp-specs-service.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/deployment-artifact.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/deployment-artifact.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/download.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/download.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/global-filters.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/global-filters.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/jwt-interceptor.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/jwt-interceptor.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/microservice-instance.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/microservice-instance.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/ms-add.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/ms-add.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/spec-validation.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/spec-validation.service.ts [new file with mode: 0644]
mod2/ui/src/app/services/user.service.spec.ts [new file with mode: 0644]
mod2/ui/src/app/services/user.service.ts [new file with mode: 0644]
mod2/ui/src/app/shared/shared-module.ts [new file with mode: 0644]
mod2/ui/src/app/user-management/user-management.component.css [new file with mode: 0644]
mod2/ui/src/app/user-management/user-management.component.html [new file with mode: 0644]
mod2/ui/src/app/user-management/user-management.component.spec.ts [new file with mode: 0644]
mod2/ui/src/app/user-management/user-management.component.ts [new file with mode: 0644]
mod2/ui/src/assets/.gitkeep [new file with mode: 0644]
mod2/ui/src/assets/env.js [new file with mode: 0644]
mod2/ui/src/environments/environment.prod.ts [new file with mode: 0644]
mod2/ui/src/environments/environment.ts [new file with mode: 0644]
mod2/ui/src/favicon.ico [new file with mode: 0644]
mod2/ui/src/index.html [new file with mode: 0644]
mod2/ui/src/main.ts [new file with mode: 0644]
mod2/ui/src/polyfills.ts [new file with mode: 0644]
mod2/ui/src/styles.css [new file with mode: 0644]
mod2/ui/src/test.ts [new file with mode: 0644]
mod2/ui/tsconfig.app.json [new file with mode: 0644]
mod2/ui/tsconfig.json [new file with mode: 0644]
mod2/ui/tsconfig.spec.json [new file with mode: 0644]
mod2/ui/tslint.json [new file with mode: 0644]
mod2/ui/typings.d.ts [new file with mode: 0644]
releases/1.5.1-blueprint-generator.yaml [new file with mode: 0644]

diff --git a/mod/bpgenerator/TestCases/imports/imports.yaml b/mod/bpgenerator/TestCases/imports/imports.yaml
new file mode 100644 (file)
index 0000000..4b52b8a
--- /dev/null
@@ -0,0 +1,2 @@
+imports: ['https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml', 'plugin:k8splugin?version=3.4.1',
+          'plugin:pgaas?version=1.3.0', 'plugin:clamppolicyplugin?version=1.1.0', 'plugin:dmaap?version=1.5.0']
diff --git a/mod/bpgenerator/TestCases/imports/importsWithBlanks.yaml b/mod/bpgenerator/TestCases/imports/importsWithBlanks.yaml
new file mode 100644 (file)
index 0000000..242c2a9
--- /dev/null
@@ -0,0 +1,3 @@
+imports: ['https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml', 'plugin:k8splugin?version=3.4.1',
+          '', ' ', '    ', 'plugin:pgaas?version=1.3.0', 'plugin:clamppolicyplugin?version=1.1.0',
+          'plugin:dmaap?version=1.5.0']
index 5d4131b..f9fa2cb 100644 (file)
  
 */
 
-
 package org.onap.blueprintgenerator.core;
 
-
-
-
+import java.util.ArrayList;
+import java.util.TreeMap;
 import lombok.Getter;
 import lombok.Setter;
+import org.onap.blueprintgenerator.models.componentspec.Artifacts;
+import org.onap.blueprintgenerator.models.componentspec.Auxilary;
+import org.onap.blueprintgenerator.models.componentspec.CallsObj;
+import org.onap.blueprintgenerator.models.componentspec.ComponentSpec;
+import org.onap.blueprintgenerator.models.componentspec.Container;
+import org.onap.blueprintgenerator.models.componentspec.HealthCheck;
+import org.onap.blueprintgenerator.models.componentspec.Host;
+import org.onap.blueprintgenerator.models.componentspec.Parameters;
+import org.onap.blueprintgenerator.models.componentspec.Policy;
+import org.onap.blueprintgenerator.models.componentspec.ProvidesObj;
+import org.onap.blueprintgenerator.models.componentspec.Publishes;
+import org.onap.blueprintgenerator.models.componentspec.Self;
+import org.onap.blueprintgenerator.models.componentspec.Services;
+import org.onap.blueprintgenerator.models.componentspec.Streams;
+import org.onap.blueprintgenerator.models.componentspec.Subscribes;
+import org.onap.blueprintgenerator.models.componentspec.Volumes;
 
 @Getter @Setter
 public class TestComponentSpec {
-       private String cs = "{\r\n" + 
+       private String componentSpecAsString = "{\r\n" +
                        "       \"self\": {\r\n" + 
                        "               \"component_type\": \"docker\",\r\n" + 
                        "               \"description\": \"Test component spec\",\r\n" + 
@@ -129,4 +143,128 @@ public class TestComponentSpec {
                        "       }]      \r\n" + 
                        "\r\n" + 
                        "}";
+       private ComponentSpec componentSpec;
+
+       public TestComponentSpec() {
+               this.componentSpec = createComponentSpec();
+       }
+
+       private ComponentSpec createComponentSpec(){
+               //Manually fill a component spec object with the values from the file itself
+               ComponentSpec manualSpec = new ComponentSpec();
+
+               Self self = new Self();
+               self.setComponent_type("docker");
+               self.setDescription("Test component spec");
+               self.setName("test.component.spec");
+               self.setVersion("1.0.1");
+               manualSpec.setSelf(self);
+
+               Services services = new Services();
+               CallsObj[] calls = new CallsObj[0];
+               ProvidesObj[] provides = new ProvidesObj[0];
+               services.setCalls(calls);
+               services.setProvides(provides);
+               manualSpec.setServices(null);
+
+               Streams streams = new Streams();
+               Publishes[] publishes = new Publishes[2];
+               Publishes pub1 = new Publishes();
+               pub1.setConfig_key("TEST-PUB-DR");
+               pub1.setFormat("dataformat_Hello_World_PM");
+               pub1.setType("data_router");
+               pub1.setVersion("1.0.0");
+
+               Publishes pub2 = new Publishes();
+               pub2.setConfig_key("TEST-PUB-MR");
+               pub2.setFormat("dataformat_Hello_World_PM");
+               pub2.setType("message_router");
+               pub2.setVersion("1.0.0");
+               publishes[0] = pub1;
+               publishes[1] = pub2;
+               streams.setPublishes(publishes);
+
+               Subscribes[] subscribes = new Subscribes[2];
+               Subscribes sub1 = new Subscribes();
+               sub1.setConfig_key("TEST-SUB-MR");
+               sub1.setFormat("dataformat_Hello_World_PM");
+               sub1.setRoute("/TEST_HELLO_WORLD_SUB_MR");
+               sub1.setType("message_router");
+               sub1.setVersion("1.0.0");
+
+               Subscribes sub2 = new Subscribes();
+               sub2.setConfig_key("TEST-SUB-DR");
+               sub2.setFormat("dataformat_Hello_World_PM");
+               sub2.setRoute("/TEST-HELLO-WORLD-SUB-DR");
+               sub2.setType("data_router");
+               sub2.setVersion("1.0.0");
+               subscribes[0] = sub1;
+               subscribes[1] = sub2;
+               streams.setSubscribes(subscribes);
+
+               manualSpec.setStreams(streams);
+
+               Parameters[] parameters = new Parameters[1];
+               Parameters par1 = new Parameters();
+               par1.setName("testParam1");
+               par1.setValue("test-param-1");
+               par1.setDescription("test parameter 1");
+               par1.setSourced_at_deployment(true);
+               par1.setDesigner_editable(true);
+               par1.setPolicy_editable(true);
+               par1.setPolicy_group("Test_Parameters");
+               par1.setRequired(true);
+               par1.setType("string");
+               parameters[0] = par1;
+
+               manualSpec.setParameters(parameters);
+
+               Auxilary auxilary = new Auxilary();
+               HealthCheck healthcheck = new HealthCheck();
+               healthcheck.setInterval("300s");
+               healthcheck.setTimeout("120s");
+               healthcheck.setScript("/etc/init.d/nagios status");
+               healthcheck.setType("docker");
+               auxilary.setHealthcheck(healthcheck);
+
+               Volumes[] volumes = new Volumes[1];
+               Volumes vol1 = new Volumes();
+               Container con1 = new Container();
+               con1.setBind("/opt/app/manager/config/hostname");
+               Host host1 = new Host();
+               host1.setPath("/etc/hostname");
+               host1.setMode("ro");
+               vol1.setContainer(con1);
+               vol1.setHost(host1);
+
+               volumes[0] = vol1;
+
+               auxilary.setVolumes(volumes);
+
+               ArrayList<Object> ports = new ArrayList();
+               ports.add("80:80");
+
+               TreeMap<String, String> dataBases = new TreeMap<>();
+               dataBases.put("TestDB1", "PGaaS");
+               dataBases.put("TestDB2", "PGaaS");
+               auxilary.setDatabases(dataBases);
+
+               Policy pol = new Policy();
+               pol.setTrigger_type("docker");
+               pol.setScript_path("/opt/app/manager/bin/reconfigure.sh");
+               auxilary.setPolicy(pol);
+
+               auxilary.setPorts(ports);
+
+               manualSpec.setAuxilary(auxilary);
+
+               Artifacts[] artifacts = new Artifacts[1];
+               Artifacts art = new Artifacts();
+               art.setType("docker image");
+               art.setUri("test.tester");
+
+               artifacts[0] = art;
+               manualSpec.setArtifacts(artifacts);
+               return manualSpec;
+       }
 }
index b39a8ec..75864be 100644 (file)
@@ -52,8 +52,7 @@ public class Appconfig {
 
        public TreeMap<String, LinkedHashMap<String, Object>> createAppconfig(TreeMap<String, LinkedHashMap<String, Object>> inps, ComponentSpec cs, String override,
                                                                                                                                                  boolean isDmaap) {
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = new TreeMap<String, LinkedHashMap<String, Object>>();
-               retInputs = inps;
+               TreeMap<String, LinkedHashMap<String, Object>> retInputs = inps;
 
                //set service calls
                CallsObj[] call = new CallsObj[0];
@@ -83,7 +82,7 @@ public class Appconfig {
                }
 
                //set the stream publishes
-               TreeMap<String, DmaapObj> streamSubscribes = new TreeMap<String, DmaapObj>();
+               TreeMap<String, DmaapObj> streamSubscribes = new TreeMap<>();
 
                if(cs.getStreams().getSubscribes().length != 0) {
                        for(Subscribes s: cs.getStreams().getSubscribes()) {
@@ -110,7 +109,7 @@ public class Appconfig {
                this.setStreams_subscribes(streamSubscribes);
 
                //set the parameters into the appconfig
-               TreeMap<String, Object> parameters = new TreeMap<String, Object>();
+               TreeMap<String, Object> parameters = new TreeMap<>();
                for(Parameters p: cs.getParameters()) {
                        String pName = p.getName();
                        if(p.isSourced_at_deployment()) {
@@ -119,17 +118,17 @@ public class Appconfig {
                                parameters.put(pName, paramInput);
 
                                if(!p.getValue().equals("")) {
-                                       LinkedHashMap<String, Object> inputs = new LinkedHashMap<String, Object>();
+                                       LinkedHashMap<String, Object> inputs = new LinkedHashMap<>();
                                        inputs.put("type", "string");
                                        inputs.put("default", p.getValue());
                                        retInputs.put(pName, inputs);
                                } else {
-                                       LinkedHashMap<String, Object> inputs = new LinkedHashMap<String, Object>();
+                                       LinkedHashMap<String, Object> inputs = new LinkedHashMap<>();
                                        inputs.put("type", "string");
                                        retInputs.put(pName, inputs);
                                }
                        } else {
-                               if(p.getType() == "string") {
+                               if("string".equals(p.getType())) {
                                        String val  =(String) p.getValue();
                                        val = '"' + val + '"';
                                        parameters.put(pName, val);
@@ -143,7 +142,7 @@ public class Appconfig {
                        GetInput ov = new GetInput();
                        ov.setBpInputName("service_component_name_override");
                        parameters.put("service_component_name_override", ov);
-                       LinkedHashMap<String, Object> over = new LinkedHashMap<String, Object>();
+                       LinkedHashMap<String, Object> over = new LinkedHashMap<>();
                        over.put("type", "string");
                        over.put("default", override);
                        retInputs.put("service_component_name_override", over);
index c043a9e..f2ef7aa 100644 (file)
 
 package org.onap.blueprintgenerator.models.blueprint;
 
-import java.io.*;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.TreeMap;
 import java.util.regex.Pattern;
-
+import lombok.Getter;
+import lombok.Setter;
 import org.onap.blueprintgenerator.core.Fixes;
 import org.onap.blueprintgenerator.models.componentspec.ComponentSpec;
-import org.onap.blueprintgenerator.models.componentspec.Parameters;
-import org.onap.blueprintgenerator.models.componentspec.Publishes;
-import org.onap.blueprintgenerator.models.componentspec.Subscribes;
 import org.onap.blueprintgenerator.models.dmaapbp.DmaapBlueprint;
 import org.onap.blueprintgenerator.models.onapbp.OnapBlueprint;
 
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.core.JsonProcessingException;
-//import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import org.yaml.snakeyaml.Yaml;
-
-
 @Getter @Setter
 @JsonInclude(JsonInclude.Include.NON_NULL)
 
@@ -100,93 +92,43 @@ public class Blueprint {
 
        public void blueprintToYaml(String outputPath, String bluePrintName, ComponentSpec cs) {
                File outputFile;
-
-               if(bluePrintName.equals("")) {
-                       String name = cs.getSelf().getName();
-                       if(name.contains(".")) {
-                               name = name.replaceAll(Pattern.quote("."), "_");
-                       }
-                       if(name.contains(" ")) {
-                               name = name.replaceAll(" ", "");
-                       }
-                       String file = name + ".yaml";
-
-
-                       outputFile = new File(outputPath, file);
-                       outputFile.getParentFile().mkdirs();
-                       try {
-                               outputFile.createNewFile();
-                       } catch (IOException e) {
-                               
-                               throw new RuntimeException(e);
-                       }
-               } else {
-                       if(bluePrintName.contains(" ") || bluePrintName.contains(".")) {
-                               bluePrintName = bluePrintName.replaceAll(Pattern.quote("."), "_");
-                               bluePrintName = bluePrintName.replaceAll(" ", "");
-                       }
-                       String file = bluePrintName + ".yaml";
-                       outputFile = new File(outputPath, file);
-                       outputFile.getParentFile().mkdirs();
-                       try {
-                               outputFile.createNewFile();
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
+               String name = bluePrintName.equals("") ? cs.getSelf().getName() : bluePrintName;
+               if(name.contains(".")) {
+                       name = name.replaceAll(Pattern.quote("."), "_");
+               }
+               if(name.contains(" ")) {
+                       name = name.replaceAll(" ", "");
+               }
+               String file = name + ".yaml";
+               outputFile = new File(outputPath, file);
+               outputFile.getParentFile().mkdirs();
+               try {
+                       outputFile.createNewFile();
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
                }
 
                String version = "#blueprint_version: " + cs.getSelf().getVersion() + '\n';
                String description = "#description: " + cs.getSelf().getDescription() + '\n';
 
-               BufferedWriter writer = null;
-               try {
-                       writer = new BufferedWriter(new FileWriter(outputFile, false));
+               try(BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile, false))) {
+                       writer.write(description);
+                       writer.write(version);
                } catch (IOException e1) {
                        throw new RuntimeException(e1);
                }
-               if(writer != null) {
-                       try {
-                               writer.write(description);
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
-                       try {
-                               writer.write(version);
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
-                       try {
-                               writer.close();
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
-               }
-
 
                //read the translated blueprint into the file
                ObjectMapper blueprintMapper = new ObjectMapper(new YAMLFactory().configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true));
 
-               PrintWriter out = null;
-               try {
-                       out = new PrintWriter(new BufferedWriter(new FileWriter(outputFile, true)));
+               try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outputFile, true)))) {
+                       blueprintMapper.writeValue(out, this);
                } catch (IOException e) {
                        throw new RuntimeException(e);
                }
 
                try {
-                       if(out != null) {
-                               blueprintMapper.writeValue(out, this);
-                               out.close();
-                       }
-               } catch (IOException e) {
-                       
-                       throw new RuntimeException(e);
-               }
-
-
-               Fixes fix = new Fixes();
-               try {
-                       fix.fixSingleQuotes(outputFile);
+                       Fixes.fixSingleQuotes(outputFile);
                } catch (IOException e) {
                        throw new RuntimeException(e);
                }
index b17b045..7b55e17 100644 (file)
 
 package org.onap.blueprintgenerator.models.blueprint;
 
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-
-
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
-import lombok.AllArgsConstructor;
-import lombok.Getter; import lombok.Setter;
-import lombok.NoArgsConstructor;
-
-
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import lombok.Getter;
+import lombok.Setter;
 
 @Getter @Setter
 @JsonInclude(value=Include.NON_NULL)
@@ -46,32 +38,30 @@ public class Imports {
        private ArrayList<String> imports;
 
        public static ArrayList<String> createOnapImports() {
-               ArrayList<String> imps = new ArrayList<String>();
-               imps.add("http://www.getcloudify.org/spec/cloudify/3.4/types.yaml");
-               imps.add("https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R6/k8splugin/1.7.2/k8splugin_types.yaml");
-               imps.add("https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R6/dcaepolicyplugin/2.4.0/dcaepolicyplugin_types.yaml");
+               ArrayList<String> imps = new ArrayList<>();
+               imps.add("https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml");
+               imps.add("plugin:k8splugin?version=3.4.2");
+               imps.add("plugin:dcaepolicyplugin?version=2.4.0");
                return imps;
        }
+
        public static ArrayList<String> createDmaapImports(){
-               ArrayList<String> imps = new ArrayList<String>();
-               imps.add("http://www.getcloudify.org/spec/cloudify/3.4/types.yaml");
-               imps.add("https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R5/k8splugin/1.6.0/k8splugin_types.yaml");
-               imps.add("https://nexus.onap.org/service/local/repositories/raw/content/org.onap.ccsdk.platform.plugins/type_files/dmaap/dmaap.yaml");
+               ArrayList<String> imps = new ArrayList<>();
+               imps.add("https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml");
+               imps.add("plugin:k8splugin?version=3.4.2");
+               imps.add("plugin:dmaap?version=1.5.0");
                return imps;
        }
+
        public static ArrayList<String> createImportsFromFile(String path) {
-               Imports imports = new Imports();
                ObjectMapper importMapper = new ObjectMapper(new YAMLFactory().configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true));
                File importPath = new File(path);
                try {
-                       imports = importMapper.readValue(importPath, Imports.class);
+                       Imports imports = importMapper.readValue(importPath, Imports.class);
+                       imports.getImports().removeIf(String::isBlank);
+                       return imports.getImports();
                } catch (IOException e) {
                        throw new RuntimeException(e);
                }
-               ArrayList<String> imps = new ArrayList<String>();
-               for(String s: imports.getImports()) {
-                       imps.add(s);
-               }
-               return imps;
        }
 }
index a3404f6..435059e 100644 (file)
@@ -31,11 +31,9 @@ import lombok.Getter; import lombok.Setter;
 public class Interfaces {
        private Start start;
        public TreeMap<String, LinkedHashMap<String, Object>> createInterface(TreeMap<String, LinkedHashMap<String, Object>> inps, ComponentSpec cs){
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = new TreeMap<String, LinkedHashMap<String, Object>>();
-               retInputs = inps;
                //create the start object
                Start start = new Start();
-               retInputs = start.createOnapStart(retInputs, cs);
+               TreeMap<String, LinkedHashMap<String, Object>> retInputs = start.createOnapStart(inps, cs);
                this.setStart(start);
                return retInputs;
        }
index 5693f86..4140ea3 100644 (file)
@@ -51,6 +51,8 @@ import org.onap.blueprintgenerator.models.dmaapbp.DmaapStreams;
 @JsonInclude(value = Include.NON_NULL)
 public class Properties {
 
+    ArrayList<DmaapStreams> streams_publishes;
+    ArrayList<DmaapStreams> streams_subscribes;
     private Appconfig application_config;
     private Auxilary docker_config;
     private Object image;
@@ -62,8 +64,6 @@ public class Properties {
     private String name;
     private GetInput topic_name;
     private GetInput feed_name;
-    ArrayList<DmaapStreams> streams_publishes;
-    ArrayList<DmaapStreams> streams_subscribes;
     private TlsInfo tls_info;
     private ExternalTlsInfo external_cert;
     private ResourceConfig resource_config;
@@ -159,8 +159,7 @@ public class Properties {
 
     public TreeMap<String, LinkedHashMap<String, Object>> createDmaapProperties(
         TreeMap<String, LinkedHashMap<String, Object>> inps, ComponentSpec cs, String override) {
-        TreeMap<String, LinkedHashMap<String, Object>> retInputs = new TreeMap<String, LinkedHashMap<String, Object>>();
-        retInputs = inps;
+        TreeMap<String, LinkedHashMap<String, Object>> retInputs = inps;
 
         //set the image
         GetInput image = new GetInput();
@@ -175,7 +174,7 @@ public class Properties {
         GetInput location = new GetInput();
         location.setBpInputName("location_id");
         this.setLocation_id(location);
-        LinkedHashMap<String, Object> locMap = new LinkedHashMap();
+        LinkedHashMap<String, Object> locMap = new LinkedHashMap<>();
         locMap.put("type", "string");
         locMap.put("default", "");
         retInputs.put("location_id", locMap);
@@ -223,7 +222,7 @@ public class Properties {
         this.setApplication_config(app);
 
         //set the stream publishes
-        ArrayList<DmaapStreams> pubStreams = new ArrayList();
+        ArrayList<DmaapStreams> pubStreams = new ArrayList<>();
         if (cs.getStreams().getPublishes() != null) {
             for (Publishes publishes : cs.getStreams().getPublishes()) {
                 if (isMessageRouterType(publishes.getType())) {
@@ -245,7 +244,7 @@ public class Properties {
         }
 
         //set the stream subscribes
-        ArrayList<DmaapStreams> subStreams = new ArrayList();
+        ArrayList<DmaapStreams> subStreams = new ArrayList<>();
         if (cs.getStreams().getSubscribes() != null) {
             for (Subscribes subscribes : cs.getStreams().getSubscribes()) {
                 if (isMessageRouterType(subscribes.getType())) {
@@ -266,10 +265,10 @@ public class Properties {
             }
         }
 
-        if (pubStreams.size() != 0) {
+        if (!pubStreams.isEmpty()) {
             this.setStreams_publishes(pubStreams);
         }
-        if (subStreams.size() != 0) {
+        if (!subStreams.isEmpty()) {
             this.setStreams_subscribes(subStreams);
         }
 
index 13aa0d0..6595a67 100644 (file)
@@ -71,13 +71,12 @@ public class ResourceConfig {
         * @return the tree map
         */
        public TreeMap<String, LinkedHashMap<String, Object>> createResourceConfig(TreeMap<String, LinkedHashMap<String, Object>> inps, String name){
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = inps;
 
-               LinkedHashMap<String, Object> mi = new LinkedHashMap<String, Object>();
+               LinkedHashMap<String, Object> mi = new LinkedHashMap<>();
                mi.put("type", "string");
                mi.put("default", "128Mi");
 
-               LinkedHashMap<String, Object> m = new LinkedHashMap<String, Object>();
+               LinkedHashMap<String, Object> m = new LinkedHashMap<>();
                m.put("type", "string");
                m.put("default", "250m");
 
@@ -87,7 +86,7 @@ public class ResourceConfig {
                }
 
                //set the limits
-               TreeMap<String, GetInput> lim = new TreeMap<String, GetInput>();
+               TreeMap<String, GetInput> lim = new TreeMap<>();
 
                GetInput cpu = new GetInput();
                cpu.setBpInputName(name + "cpu_limit");
@@ -97,13 +96,13 @@ public class ResourceConfig {
                memL.setBpInputName(name + "memory_limit");
                lim.put("memory", memL);
 
-               retInputs.put(name + "cpu_limit", m);
-               retInputs.put(name + "memory_limit", mi);
+               inps.put(name + "cpu_limit", m);
+               inps.put(name + "memory_limit", mi);
 
                this.setLimits(lim);
 
                //set the requests
-               TreeMap<String, GetInput> req = new TreeMap<String, GetInput>();
+               TreeMap<String, GetInput> req = new TreeMap<>();
 
                GetInput cpuR = new GetInput();
                cpuR.setBpInputName(name + "cpu_request");
@@ -113,12 +112,12 @@ public class ResourceConfig {
                memR.setBpInputName(name + "memory_request");
                req.put("memory", memR);
 
-               retInputs.put(name + "cpu_request", m);
-               retInputs.put(name + "memory_request", mi);
+               inps.put(name + "cpu_request", m);
+               inps.put(name + "memory_request", mi);
 
                this.setRequests(req);
 
-               return retInputs;
+               return inps;
        }
 }
 
index d9c885d..0b03777 100644 (file)
@@ -36,14 +36,12 @@ public class Start {
        private LinkedHashMap<String, Object> envs;
        
        public TreeMap<String, LinkedHashMap<String, Object>> createOnapStart(TreeMap<String, LinkedHashMap<String, Object>> inps, ComponentSpec cs) {
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = inps;
-               retInputs = inps;
-               
+
                //create the start inputs
                StartInputs inputs = new StartInputs();
-               inputs.createOnapStartInputs(retInputs, cs);
+               inputs.createOnapStartInputs(inps, cs);
                this.setInputs(inputs);
                
-               return retInputs;
+               return inps;
        }
 }
index 1055fbd..a0cfe20 100644 (file)
@@ -41,10 +41,9 @@ public class StartInputs {
        private Object envs;
 
        public TreeMap<String, LinkedHashMap<String, Object>> createOnapStartInputs(TreeMap<String, LinkedHashMap<String, Object>> inps, ComponentSpec cs){
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = inps;
 
                int count = 0;
-               ArrayList<String> portList = new ArrayList();
+               ArrayList<String> portList = new ArrayList<>();
                Auxilary aux = cs.getAuxilary();
 
                if (aux.getPorts() != null) {
@@ -56,10 +55,10 @@ public class StartInputs {
                                                , ports[0], count);
                                portList.add(internal);
 
-                               LinkedHashMap<String, Object> portType = new LinkedHashMap();
+                               LinkedHashMap<String, Object> portType = new LinkedHashMap<>();
                                portType.put("type", "string");
                                portType.put("default", ports[1]);
-                               retInputs.put("external_port_" + count, portType);
+                               inps.put("external_port_" + count, portType);
 
                                count++;
                        }
@@ -98,7 +97,7 @@ public class StartInputs {
 //             }
 
                //set the envs
-               LinkedHashMap<String, Object> eMap = new LinkedHashMap();
+               LinkedHashMap<String, Object> eMap = new LinkedHashMap<>();
                if(cs.getAuxilary().getDatabases() != null){
                        //set db env variables
                        LinkedHashMap<String, Object> envVars = PgaasNodeBuilder.getEnvVariables(cs.getAuxilary().getDatabases());
@@ -111,9 +110,9 @@ public class StartInputs {
                        this.setEnvs(env);
                        eMap.put("default", "{}");
                }
-               retInputs.put("envs", eMap);
+               inps.put("envs", eMap);
 
 
-               return retInputs;
+               return inps;
        }
 }
index 464fc4a..0cec284 100644 (file)
@@ -46,9 +46,7 @@ public class DmaapInfo {
 
     public TreeMap<String, LinkedHashMap<String, Object>> createOnapDmaapMRInfo(
         TreeMap<String, LinkedHashMap<String, Object>> inps, String config, char type) {
-        TreeMap<String, LinkedHashMap<String, Object>> retInputs = new TreeMap<String, LinkedHashMap<String, Object>>();
-        retInputs = inps;
-        LinkedHashMap<String, Object> stringType = new LinkedHashMap<String, Object>();
+        LinkedHashMap<String, Object> stringType = new LinkedHashMap<>();
         stringType.put("type", "string");
 
         config = config.replaceAll("-", "_");
@@ -62,9 +60,9 @@ public class DmaapInfo {
         topic.setBpInputName(config);
         this.setTopic_url(topic);
 
-        retInputs.put(config, stringType);
+        inps.put(config, stringType);
 
-        return retInputs;
+        return inps;
     }
 
     public TreeMap<String, LinkedHashMap<String, Object>> createOnapDmaapDRInfo(
index 6af69e5..30f59e2 100644 (file)
@@ -40,10 +40,8 @@ public class DmaapObj {
 
        public TreeMap<String, LinkedHashMap<String, Object>> createOnapDmaapMRObj(TreeMap<String, LinkedHashMap<String, Object>> inps,
                                                                                                                                                           String config, char type, String n, String num, boolean isDmaap) {
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = new TreeMap<String, LinkedHashMap<String, Object>>();
-               LinkedHashMap<String, Object> stringType = new LinkedHashMap();
+               LinkedHashMap<String, Object> stringType = new LinkedHashMap<>();
                stringType.put("type", "string");
-               retInputs = inps;
 
                //set the dmaapinfo
                DmaapInfo info = new DmaapInfo();
@@ -58,20 +56,18 @@ public class DmaapObj {
                        GetInput u = new GetInput();
                        u.setBpInputName(config + "_" + num +"_aaf_username");
                        this.setUser(u);
-                       retInputs.put(config + "_" + num +"_aaf_username", stringType);
+                       inps.put(config + "_" + num +"_aaf_username", stringType);
 
                        //set password
                        GetInput p = new GetInput();
                        p.setBpInputName(config + "_" + num +"_aaf_password");
                        this.setPass(p);
-                       retInputs.put(config + "_" + num +"_aaf_password", stringType);
+                       inps.put(config + "_" + num +"_aaf_password", stringType);
                }
-               return retInputs;
+               return inps;
        }
        public TreeMap<String, LinkedHashMap<String, Object>> createOnapDmaapDRObj(TreeMap<String, LinkedHashMap<String, Object>> inps, String config, char type, String n, String num, boolean isDmaap) {
-               TreeMap<String, LinkedHashMap<String, Object>> retInputs = new TreeMap<String, LinkedHashMap<String, Object>>();
-               retInputs = inps;
-               
+
                //set the dmaapinfo
                DmaapInfo info = new DmaapInfo();
                if(!isDmaap){
@@ -82,6 +78,6 @@ public class DmaapObj {
                        String infoType = "<<" + n + ">>";
                        this.setDmaap_info(infoType);
                }
-               return retInputs;
+               return inps;
        }
 }
index f7f5a03..198dc19 100644 (file)
 package org.onap.blueprintgenerator.models.componentspec;
 
 
-
 import com.fasterxml.jackson.annotation.JsonInclude;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -53,6 +51,7 @@ import lombok.NoArgsConstructor;
  */
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
+@EqualsAndHashCode
 //Called in Component Spec Object
 public class Artifacts {
        
index a36deb5..58e1e0c 100644 (file)
 package org.onap.blueprintgenerator.models.componentspec;
 
 
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.TreeMap;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import java.util.ArrayList;
+import java.util.TreeMap;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -64,6 +60,7 @@ import lombok.NoArgsConstructor;
  */
 
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 //Called in component Spec Object
 public class Auxilary {
        
index 0b7dcee..d130632 100644 (file)
@@ -24,11 +24,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
-import lombok.NoArgsConstructor;
-
 
 // TODO: Auto-generated Javadoc
 /**
index 3615605..785df35 100644 (file)
 
 package org.onap.blueprintgenerator.models.componentspec;
 
-import java.util.HashMap;
-
-
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
-import lombok.NoArgsConstructor;
-
 // TODO: Auto-generated Javadoc
 /**
  * The Class ConstraintsObj.
index d94e2bb..cbe02dc 100644 (file)
@@ -22,11 +22,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -50,6 +49,7 @@ import lombok.NoArgsConstructor;
  */
 
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 public class Container {
        
        /** The bind. */
index 45f9091..ef33aaf 100644 (file)
@@ -24,11 +24,9 @@ package org.onap.blueprintgenerator.models.componentspec;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /**
index acc9379..76e1036 100644 (file)
@@ -23,11 +23,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -53,6 +52,7 @@ import lombok.NoArgsConstructor;
  */
 
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 //Called in Auxillary Object
 public class HealthCheck {
        
index 91734ad..f178c94 100644 (file)
@@ -22,11 +22,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -46,6 +45,7 @@ import lombok.NoArgsConstructor;
  * @param mode the mode
  */
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 public class Host{
        
        /** The path. */
index 515ecc7..eb95202 100644 (file)
 
 package org.onap.blueprintgenerator.models.componentspec;
 
-import java.util.ArrayList;
-
-
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /**
@@ -68,6 +64,7 @@ import lombok.NoArgsConstructor;
  * @param constraints the constraints
  */
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 //Called in component Spec Object
 public class Parameters {
        
index 3cc14fe..d295ca4 100644 (file)
@@ -23,11 +23,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -51,6 +50,7 @@ import lombok.NoArgsConstructor;
  * @param script_path the script path
  */
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 //called in auxilary
 public class Policy {
        
index 7af16d6..aa38271 100644 (file)
@@ -24,11 +24,9 @@ package org.onap.blueprintgenerator.models.componentspec;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /**
index ad9c87d..9ad24c3 100644 (file)
 package org.onap.blueprintgenerator.models.componentspec;
 
 
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
index 3ee47b9..4f46313 100644 (file)
@@ -23,11 +23,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -54,6 +53,7 @@ import lombok.NoArgsConstructor;
  */
 @JsonInclude(value=Include.NON_NULL)
 @JsonIgnoreProperties(ignoreUnknown = true)
+@EqualsAndHashCode
 //Called in Streams Object
 public class Publishes {
 
index 6c389d6..6d78805 100644 (file)
@@ -23,11 +23,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -50,7 +49,7 @@ import lombok.NoArgsConstructor;
  * @param policy the policy
  */
 @JsonInclude(value=Include.NON_NULL)
-
+@EqualsAndHashCode
 public class ReconfigsObj {
        
        /** The dti. */
index 2aaf60b..7ca1830 100644 (file)
@@ -25,11 +25,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
-import lombok.NoArgsConstructor;
-
 
 // TODO: Auto-generated Javadoc
 /**
index a566661..8b5f35e 100644 (file)
 
 package org.onap.blueprintgenerator.models.componentspec;
 
-import java.util.Map;
-
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -55,6 +52,7 @@ import lombok.NoArgsConstructor;
  */
 @JsonInclude(value=Include.NON_NULL)
 @JsonIgnoreProperties(ignoreUnknown = true)
+@EqualsAndHashCode
 //called in Component Spec object
 public class Self {
        
index afe0904..772d859 100644 (file)
@@ -24,11 +24,9 @@ package org.onap.blueprintgenerator.models.componentspec;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
index 1b7dcd3..500f32d 100644 (file)
@@ -24,11 +24,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -52,6 +51,7 @@ import lombok.NoArgsConstructor;
  */
 @JsonInclude(value=Include.NON_NULL)
 @JsonIgnoreProperties(ignoreUnknown = true)
+@EqualsAndHashCode
 //Called in Component Spec Object
 public class Streams {
        
index 14485a0..ec4aa41 100644 (file)
@@ -23,11 +23,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -57,6 +56,7 @@ import lombok.NoArgsConstructor;
 
 @JsonInclude(value=Include.NON_NULL)
 @JsonIgnoreProperties(ignoreUnknown = true)
+@EqualsAndHashCode
 //Called in Streams Object
 public class Subscribes {
        
index 1034242..777bdeb 100644 (file)
@@ -23,11 +23,10 @@ package org.onap.blueprintgenerator.models.componentspec;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter; import lombok.Setter;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 // TODO: Auto-generated Javadoc
 /* (non-Javadoc)
@@ -50,6 +49,7 @@ import lombok.NoArgsConstructor;
  * @param host the host
  */
 @JsonInclude(value=Include.NON_NULL)
+@EqualsAndHashCode
 //Called in Auxillary Object
 public class Volumes {
        
@@ -58,4 +58,4 @@ public class Volumes {
        
        /** The host. */
        private Host host;
-}
\ No newline at end of file
+}
index 1c7e592..b978701 100644 (file)
 
 package org.onap.blueprintgenerator.core;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import java.io.IOException;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -39,22 +38,7 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.onap.blueprintgenerator.models.blueprint.Blueprint;
 import org.onap.blueprintgenerator.models.blueprint.GetInput;
-import org.onap.blueprintgenerator.models.componentspec.Artifacts;
-import org.onap.blueprintgenerator.models.componentspec.Auxilary;
-import org.onap.blueprintgenerator.models.componentspec.CallsObj;
 import org.onap.blueprintgenerator.models.componentspec.ComponentSpec;
-import org.onap.blueprintgenerator.models.componentspec.Container;
-import org.onap.blueprintgenerator.models.componentspec.HealthCheck;
-import org.onap.blueprintgenerator.models.componentspec.Host;
-import org.onap.blueprintgenerator.models.componentspec.Parameters;
-import org.onap.blueprintgenerator.models.componentspec.Policy;
-import org.onap.blueprintgenerator.models.componentspec.ProvidesObj;
-import org.onap.blueprintgenerator.models.componentspec.Publishes;
-import org.onap.blueprintgenerator.models.componentspec.Self;
-import org.onap.blueprintgenerator.models.componentspec.Services;
-import org.onap.blueprintgenerator.models.componentspec.Streams;
-import org.onap.blueprintgenerator.models.componentspec.Subscribes;
-import org.onap.blueprintgenerator.models.componentspec.Volumes;
 import org.onap.blueprintgenerator.models.dmaapbp.DmaapNode;
 import org.onap.blueprintgenerator.models.onapbp.OnapNode;
 import org.onap.blueprintgenerator.models.policymodel.PolicyModel;
@@ -70,146 +54,20 @@ public class BlueprintGeneratorTest {
     /**
      * Component spec test.
      *
-     * @throws JsonParseException   the json parse exception
-     * @throws JsonMappingException the json mapping exception
-     * @throws IOException          Signals that an I/O exception has occurred.
      */
-
     @Test
-    public void componentSpecTest() throws JsonParseException, JsonMappingException, IOException {
-
+    public void componentSpecTest() {
         ComponentSpec spec = new ComponentSpec();
         TestComponentSpec test = new TestComponentSpec();
-        spec.createComponentSpecFromString(test.getCs());
-
-        //Manually fill a component spec object with the values from the file itself
-        ComponentSpec manualSpec = new ComponentSpec();
-
-        Self self = new Self();
-        self.setComponent_type("docker");
-        self.setDescription("Test component spec");
-        self.setName("test.component.spec");
-        self.setVersion("1.0.1");
-        manualSpec.setSelf(self);
-
-        //assertEquals(manualSpec.getSelf(), spec.getSelf());
-
-        Services services = new Services();
-        CallsObj[] calls = new CallsObj[0];
-        ProvidesObj[] provides = new ProvidesObj[0];
-        services.setCalls(calls);
-        services.setProvides(provides);
-        manualSpec.setServices(null);
-
-        //assertEquals(manualSpec.getServices(), spec.getServices());
-
-        Streams streams = new Streams();
-        Publishes[] publishes = new Publishes[2];
-        Publishes pub1 = new Publishes();
-        pub1.setConfig_key("TEST-PUB-DR");
-        pub1.setFormat("dataformat_Hello_World_PM");
-        pub1.setType("data_router");
-        pub1.setVersion("1.0.0");
-
-        Publishes pub2 = new Publishes();
-        pub2.setConfig_key("TEST-PUB-MR");
-        pub2.setFormat("dataformat_Hello_World_PM");
-        pub2.setType("message_router");
-        pub2.setVersion("1.0.0");
-        publishes[0] = pub1;
-        publishes[1] = pub2;
-        streams.setPublishes(publishes);
-
-        Subscribes[] subscribes = new Subscribes[2];
-        Subscribes sub1 = new Subscribes();
-        sub1.setConfig_key("TEST-SUB-MR");
-        sub1.setFormat("dataformat_Hello_World_PM");
-        sub1.setRoute("/TEST_HELLO_WORLD_SUB_MR");
-        sub1.setType("message_router");
-        sub1.setVersion("1.0.0");
-
-        Subscribes sub2 = new Subscribes();
-        sub2.setConfig_key("TEST-SUB-DR");
-        sub2.setFormat("dataformat_Hello_World_PM");
-        sub2.setRoute("/TEST-HELLO-WORLD-SUB-DR");
-        sub2.setType("data_router");
-        sub2.setVersion("1.0.0");
-        subscribes[0] = sub1;
-        subscribes[1] = sub2;
-        streams.setSubscribes(subscribes);
-
-        manualSpec.setStreams(streams);
-
-        //assertEquals(manualSpec.getStreams(), spec.getStreams());
-
-        Parameters[] parameters = new Parameters[1];
-        Parameters par1 = new Parameters();
-        par1.setName("testParam1");
-        par1.setValue("test-param-1");
-        par1.setDescription("test parameter 1");
-        par1.setSourced_at_deployment(true);
-        par1.setDesigner_editable(true);
-        par1.setPolicy_editable(true);
-        par1.setPolicy_group("Test_Parameters");
-        par1.setRequired(true);
-        par1.setType("string");
-        parameters[0] = par1;
-
-        manualSpec.setParameters(parameters);
-
-        //assertEquals(manualSpec.getParameters(), spec.getParameters());
-
-        Auxilary auxilary = new Auxilary();
-        HealthCheck healthcheck = new HealthCheck();
-        healthcheck.setInterval("300s");
-        healthcheck.setTimeout("120s");
-        healthcheck.setScript("/etc/init.d/nagios status");
-        healthcheck.setType("docker");
-        auxilary.setHealthcheck(healthcheck);
-
-        Volumes[] volumes = new Volumes[1];
-        Volumes vol1 = new Volumes();
-        Container con1 = new Container();
-        con1.setBind("/opt/app/manager/config/hostname");
-        Host host1 = new Host();
-        host1.setPath("/etc/hostname");
-        host1.setMode("ro");
-        vol1.setContainer(con1);
-        vol1.setHost(host1);
-
-        volumes[0] = vol1;
-
-        auxilary.setVolumes(volumes);
-
-        ArrayList<Object> ports = new ArrayList();
-        ports.add("80:90");
-        ports.add("99:99");
-
-        TreeMap<String, String> dataBases = new TreeMap<String, String>();
-        dataBases.put("TestDB1", "PGaaS");
-        dataBases.put("TestDB2", "PGaaS");
-        auxilary.setDatabases(dataBases);
-
-        Policy pol = new Policy();
-        pol.setTrigger_type("docker");
-        pol.setScript_path("/opt/app/manager/bin/reconfigure.sh");
-        auxilary.setPolicy(pol);
-
-        auxilary.setPorts(ports);
-
-        manualSpec.setAuxilary(auxilary);
-
-        //assertEquals(manualSpec.getAuxilary(), spec.getAuxilary());
-
-        Artifacts[] artifacts = new Artifacts[1];
-        Artifacts art = new Artifacts();
-        art.setType("docker image");
-        art.setUri("test.tester");
-
-        artifacts[0] = art;
-        manualSpec.setArtifacts(artifacts);
-
-        //assertEquals(manualSpec.getArtifacts(), spec.getArtifacts());
+        spec.createComponentSpecFromString(test.getComponentSpecAsString());
+        ComponentSpec expectedSpec = test.getComponentSpec();
+
+        assertEquals(expectedSpec.getSelf(), spec.getSelf());
+        assertEquals(expectedSpec.getServices(), spec.getServices());
+        assertEquals(expectedSpec.getStreams(), spec.getStreams());
+        assertArrayEquals(expectedSpec.getParameters(), spec.getParameters());
+        assertEquals(expectedSpec.getAuxilary(), spec.getAuxilary());
+        assertArrayEquals(expectedSpec.getArtifacts(), spec.getArtifacts());
     }
 
     /**
@@ -219,11 +77,11 @@ public class BlueprintGeneratorTest {
     public void toscaDefinitionTest() {
         ComponentSpec cs = new ComponentSpec();
         TestComponentSpec test = new TestComponentSpec();
-        cs.createComponentSpecFromString(test.getCs());
+        cs.createComponentSpecFromString(test.getComponentSpecAsString());
         Blueprint bp = new Blueprint();
         bp = bp.createBlueprint(cs, "", 'o', "", "");
 
-        assertEquals(bp.getTosca_definitions_version(), "cloudify_dsl_1_3");
+        assertEquals("cloudify_dsl_1_3", bp.getTosca_definitions_version());
     }
 
     /**
@@ -233,19 +91,16 @@ public class BlueprintGeneratorTest {
     public void importsTest() {
         ComponentSpec cs = new ComponentSpec();
         TestComponentSpec test = new TestComponentSpec();
-        cs.createComponentSpecFromString(test.getCs());
+        cs.createComponentSpecFromString(test.getComponentSpecAsString());
 
         Blueprint bp = new Blueprint();
         bp = bp.createBlueprint(cs, "", 'o', "", "");
 
-        ArrayList<String> imps = new ArrayList<String>();
-
-        imps.add("http://www.getcloudify.org/spec/cloudify/3.4/types.yaml");
-        imps.add(
-            "https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R6/k8splugin/1.7.2/k8splugin_types.yaml");
-        imps.add(
-            "https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R6/dcaepolicyplugin/2.4.0/dcaepolicyplugin_types.yaml");
-        assertEquals(bp.getImports(), imps);
+        ArrayList<String> imps = new ArrayList<>();
+        imps.add("https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml");
+        imps.add("plugin:k8splugin?version=3.4.2");
+        imps.add("plugin:dcaepolicyplugin?version=2.4.0");
+        assertEquals(imps, bp.getImports());
     }
 
     @Test
@@ -256,50 +111,49 @@ public class BlueprintGeneratorTest {
         Blueprint bp = new Blueprint();
         bp = bp.createBlueprint(cs, "", 'o', "", "");
 
-        TreeMap<String, LinkedHashMap<String, Object>> inputs = new TreeMap<String, LinkedHashMap<String, Object>>();
+        TreeMap<String, LinkedHashMap<String, Object>> inputs = new TreeMap<>();
 
         //mr inputs
-        LinkedHashMap<String, Object> stringType = new LinkedHashMap<String, Object>();
+        LinkedHashMap<String, Object> stringType = new LinkedHashMap<>();
         stringType.put("type", "string");
 
         //necessary inputs
-        LinkedHashMap<String, Object> tag = new LinkedHashMap<String, Object>();
+        LinkedHashMap<String, Object> tag = new LinkedHashMap<>();
         tag.put("type", "string");
         String tester = "test.tester";
         tag.put("default", '"' + tester + '"');
-        String tagVersion = "tag_version";
         inputs.put("tag_version", tag);
 
         inputs.put("log_directory", stringType);
 
-        LinkedHashMap cert = new LinkedHashMap();
+        LinkedHashMap<String, Object> cert = new LinkedHashMap<>();
         cert.put("type", "string");
         cert.put("default", "");
         inputs.put("cert_directory", cert);
 
-        LinkedHashMap<String, Object> env = new LinkedHashMap();
+        LinkedHashMap<String, Object> env = new LinkedHashMap<>();
         env.put("default", "{}");
         inputs.put("envs", env);
 
-        LinkedHashMap port = new LinkedHashMap();
+        LinkedHashMap<String, Object> port = new LinkedHashMap<>();
         port.put("type", "string");
         port.put("description", "Kubernetes node port on which collector is exposed");
         port.put("default", "99");
         inputs.put("external_port", port);
 
-        LinkedHashMap<String, Object> rep = new LinkedHashMap<String, Object>();
+        LinkedHashMap<String, Object> rep = new LinkedHashMap<>();
         rep.put("type", "integer");
         rep.put("description", "number of instances");
         rep.put("default", 1);
         inputs.put("replicas", rep);
 
-        LinkedHashMap<String, Object> aaf = new LinkedHashMap();
+        LinkedHashMap<String, Object> aaf = new LinkedHashMap<>();
         aaf.put("type", "boolean");
         aaf.put("default", false);
         inputs.put("use_tls", aaf);
 
         //parmaeter input
-        LinkedHashMap<String, Object> test = new LinkedHashMap<String, Object>();
+        LinkedHashMap<String, Object> test = new LinkedHashMap<>();
         test.put("type", "string");
         String testParam = "test-param-1";
         test.put("default", '"' + testParam + '"');
@@ -325,19 +179,17 @@ public class BlueprintGeneratorTest {
         inputs.put("topic0_name", stringType);
         inputs.put("topic1_name", stringType);
 
-        LinkedHashMap<String, Object> cpu = new LinkedHashMap();
+        LinkedHashMap<String, Object> cpu = new LinkedHashMap<>();
         cpu.put("type", "string");
         cpu.put("default", "250m");
         inputs.put("test.component.spec_cpu_limit", cpu);
         inputs.put("test.component.spec_cpu_request", cpu);
 
-        LinkedHashMap<String, Object> mem = new LinkedHashMap();
+        LinkedHashMap<String, Object> mem = new LinkedHashMap<>();
         mem.put("type", "string");
         mem.put("default", "128Mi");
         inputs.put("test.component.spec_memory_limit", mem);
         inputs.put("test.component.spec_memory_request", mem);
-
-        assertEquals(true, true);
     }
 
     @Test
@@ -355,10 +207,9 @@ public class BlueprintGeneratorTest {
         //set the type
         testNode.setType("dcae.nodes.ContainerizedServiceComponent");
 
-        ArrayList<String> ports = new ArrayList<String>();
+        ArrayList<String> ports = new ArrayList<>();
         ports.add("concat: [\"80:\", {get_input: external_port }]");
         ports.add("concat: [\"99:\", {get_input: external_port }]");
-        assertEquals(true, true);
     }
 
     @Test
@@ -372,7 +223,7 @@ public class BlueprintGeneratorTest {
         OnapNode node = (OnapNode) bp.getNode_templates().get("test.component.spec");
 
         GetInput par = (GetInput) node.getProperties().getApplication_config().getParams().get("testParam1");
-        assertEquals(par.getBpInputName(), "testParam1");
+        assertEquals("testParam1", par.getBpInputName());
     }
 
     @Test
@@ -385,13 +236,7 @@ public class BlueprintGeneratorTest {
 
         OnapNode node = (OnapNode) bp.getNode_templates().get("test.component.spec");
 
-        boolean test = false;
-        if (!node.getProperties().getApplication_config().getStreams_publishes().isEmpty()) {
-            test = true;
-            System.out.println("tst");
-        }
-
-        assertEquals(true, test);
+        assertFalse(node.getProperties().getApplication_config().getStreams_publishes().isEmpty());
     }
 
     @Test
@@ -405,13 +250,8 @@ public class BlueprintGeneratorTest {
         DmaapNode dmaap = (DmaapNode) bp.getNode_templates().get("test.component.spec");
 
         //check if the stream publishes and subscribes are not null to see if the dmaap plugin was invoked properly
-        boolean d = false;
-
-        if (dmaap.getProperties().getStreams_publishes() != null
-            || dmaap.getProperties().getStreams_subscribes() != null) {
-            d = true;
-        }
-        assertEquals(true, d);
+        assertNotNull(dmaap.getProperties().getStreams_publishes());
+        assertNotNull(dmaap.getProperties().getStreams_subscribes());
     }
 
     @Test
@@ -442,17 +282,5 @@ public class BlueprintGeneratorTest {
 
         PolicyModel p = new PolicyModel();
         p.createPolicyModels(cs, "TestModels");
-
-        assertEquals(true, true);
-    }
-
-    private void assertContainsInputWithDefault(Blueprint bp, String inputName, Object defaultValue) {
-        LinkedHashMap<String, Object> input = bp.getInputs().get(inputName);
-        assertNotNull(input);
-        assertEquals(defaultValue, input.get("default"));
-    }
-
-    private String inQuotes(String filedName) {
-        return String.format("\"%s\"", filedName);
     }
 }
diff --git a/mod/bpgenerator/src/test/java/org/onap/blueprintgenerator/models/blueprint/ImportsTest.java b/mod/bpgenerator/src/test/java/org/onap/blueprintgenerator/models/blueprint/ImportsTest.java
new file mode 100644 (file)
index 0000000..1ce296a
--- /dev/null
@@ -0,0 +1,52 @@
+/*============LICENSE_START=======================================================
+ org.onap.dcae
+ ================================================================================
+ Copyright (c) 2020 Nokia Intellectual Property. 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=========================================================
+ */
+
+package org.onap.blueprintgenerator.models.blueprint;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class ImportsTest {
+
+    private final List<String> expectedImports = Arrays.asList(
+        "https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml",
+        "plugin:k8splugin?version=3.4.1",
+        "plugin:pgaas?version=1.3.0",
+        "plugin:clamppolicyplugin?version=1.1.0",
+        "plugin:dmaap?version=1.5.0"
+    );
+
+    @Test
+    public void shouldReadImportsFromFile() {
+        ArrayList<String> importsFromFile = Imports.createImportsFromFile("TestCases/imports/imports.yaml");
+        assertEquals(expectedImports, importsFromFile);
+    }
+
+    @Test
+    public void shouldRemoveBlankImportsFromFile() {
+        ArrayList<String> importsFromFile =
+            Imports.createImportsFromFile("TestCases/imports/importsWithBlanks.yaml");
+        assertEquals(expectedImports, importsFromFile);
+    }
+
+}
index e854b19..1cdb58b 100644 (file)
@@ -25,7 +25,7 @@ import org.onap.blueprintgenerator.models.blueprint.tls.impl.ExternalCertificate
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 import static org.onap.blueprintgenerator.models.blueprint.tls.TlsConstants.COMMON_NAME_FIELD;
 import static org.onap.blueprintgenerator.models.blueprint.tls.TlsConstants.DEFAULT_COMMON_NAME;
 import static org.onap.blueprintgenerator.models.blueprint.tls.TlsConstants.DEFAULT_SANS;
index 75b68ce..112a9d7 100644 (file)
@@ -34,7 +34,7 @@ limitations under the License.
        </parent>
        <groupId>org.onap.dcaegen2.platform.mod</groupId>
        <artifactId>runtimeapi</artifactId>
-       <version>1.0.2</version>
+       <version>1.1.0</version>
        <name>dcaegen2-platform-mod-runtimeapi</name>
        <description>MOD Runtime API</description>
        <properties>
index 226d4e8..15eda39 100644 (file)
@@ -25,12 +25,12 @@ limitations under the License.
     <parent>
         <artifactId>runtimeapi</artifactId>
         <groupId>org.onap.dcaegen2.platform.mod</groupId>
-        <version>1.0.2</version>
+        <version>1.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>runtime-core</artifactId>
-    <version>1.0.1</version>
+    <version>1.1.0</version>
 
     <dependencies>
         <dependency>
@@ -46,7 +46,7 @@ limitations under the License.
         <dependency>
             <groupId>org.onap.dcaegen2.platform.mod</groupId>
             <artifactId>blueprint-generator</artifactId>
-            <version>1.4.0</version>
+            <version>1.5.1</version>
         </dependency>
        <dependency>
                <groupId>org.json</groupId>
index c8ce84a..6842cba 100644 (file)
@@ -24,10 +24,10 @@ limitations under the License.
        <parent>
                <groupId>org.onap.dcaegen2.platform.mod</groupId>
                <artifactId>runtimeapi</artifactId>
-               <version>1.0.2</version>
+               <version>1.1.0</version>
        </parent>
        <artifactId>runtime-web</artifactId>
-       <version>1.0.6-SNAPSHOT</version>
+       <version>1.1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <name>runtime-web</name>
        <description>MOD Runtime Web Module</description>
@@ -35,7 +35,7 @@ limitations under the License.
                <dependency>
                        <groupId>org.onap.dcaegen2.platform.mod</groupId>
                        <artifactId>runtime-core</artifactId>
-                       <version>1.0.1</version>
+                       <version>1.1.0</version>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
diff --git a/mod2/auth-service/Dockerfile b/mod2/auth-service/Dockerfile
new file mode 100644 (file)
index 0000000..c427bda
--- /dev/null
@@ -0,0 +1,11 @@
+FROM onap/integration-java11:7.1.0
+WORKDIR /usr/app
+VOLUME /tmp
+
+ADD target/auth-service-1.0.0-SNAPSHOT.jar auth-service-1.0.0-SNAPSHOT.jar
+
+EXPOSE 8082
+
+ENTRYPOINT ["java", \
+            "-Djava.security.egd=file:/dev/./urandom", \
+            "-jar", "auth-service-1.0.0-SNAPSHOT.jar"]
\ No newline at end of file
diff --git a/mod2/auth-service/pom.xml b/mod2/auth-service/pom.xml
new file mode 100644 (file)
index 0000000..1fd4f02
--- /dev/null
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ /*
+  ~  * ============LICENSE_START=======================================================
+  ~  *  org.onap.dcae
+  ~  *  ================================================================================
+  ~  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+  ~  */
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.onap.oparent</groupId>
+               <artifactId>oparent</artifactId>
+               <version>2.0.0</version>
+       </parent>
+       <groupId>org.onap.dcaegen2.platform.mod</groupId>
+       <artifactId>auth-service</artifactId>
+       <version>1.0.0-SNAPSHOT</version>
+       <name>auth-service</name>
+       <description>REST APIs to serve Auth Service</description>
+
+       <properties>
+               <java.version>11</java.version>
+               <maven.compiler.source>${java.version}</maven.compiler.source>
+               <maven.compiler.target>${java.version}</maven.compiler.target>
+       </properties>
+
+       <dependencies>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-data-mongodb</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>com.google.code.gson</groupId>
+                       <artifactId>gson</artifactId>
+                       <version>2.8.6</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-security</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>io.jsonwebtoken</groupId>
+                       <artifactId>jjwt</artifactId>
+                       <version>0.9.1</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-web</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-webflux</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-validation</artifactId>
+               </dependency>
+               <!-- CODE GENERATION -->
+               <dependency>
+                       <groupId>org.projectlombok</groupId>
+                       <artifactId>lombok</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>com.squareup.okhttp3</groupId>
+                       <artifactId>okhttp</artifactId>
+                       <version>4.0.1</version>
+               </dependency>
+
+               <!--TEST DEPENDENCIES-->
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-test</artifactId>
+                       <scope>test</scope>
+                       <exclusions>
+                               <exclusion>
+                                       <groupId>org.junit.vintage</groupId>
+                                       <artifactId>junit-vintage-engine</artifactId>
+                               </exclusion>
+                       </exclusions>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.security</groupId>
+                       <artifactId>spring-security-test</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>com.squareup.okhttp3</groupId>
+                       <artifactId>mockwebserver</artifactId>
+                       <version>4.0.1</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.security</groupId>
+                       <artifactId>spring-security-test</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>io.projectreactor</groupId>
+                       <artifactId>reactor-test</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>javax.xml.bind</groupId>
+                       <artifactId>jaxb-api</artifactId>
+                       <version>2.3.1</version>
+               </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>RELEASE</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+       <dependencyManagement>
+               <dependencies>
+                       <dependency>
+                               <!-- Import dependency management from Spring Boot -->
+                               <groupId>org.springframework.boot</groupId>
+                               <artifactId>spring-boot-dependencies</artifactId>
+                               <version>2.2.5.RELEASE</version>
+                               <type>pom</type>
+                               <scope>import</scope>
+                       </dependency>
+               </dependencies>
+       </dependencyManagement>
+
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.springframework.boot</groupId>
+                               <artifactId>spring-boot-maven-plugin</artifactId>
+                               <version>2.2.5.RELEASE</version>
+                               <executions>
+                                       <execution>
+                                               <goals>
+                                                       <goal>repackage</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-surefire-plugin</artifactId>
+                               <version>2.22.2</version>
+                       </plugin>
+               </plugins>
+       </build>
+</project>
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/AuthServiceApplication.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/AuthServiceApplication.java
new file mode 100644 (file)
index 0000000..5b147bf
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Auth Service Application
+ */
+@SpringBootApplication
+public class AuthServiceApplication {
+
+       public static void main(String[] args) {
+               SpringApplication.run(AuthServiceApplication.class, args);
+       }
+
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/DataLoader.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/DataLoader.java
new file mode 100644 (file)
index 0000000..959b450
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod;
+
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+import org.onap.dcaegen2.platform.mod.repositories.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * To Initialize Roles and create Default Admin User
+ */
+@Component
+public class DataLoader implements ApplicationRunner {
+
+    @Autowired
+    private RoleRepository roleRepository;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    PasswordEncoder passwordEncoder;
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        populateRoles();
+        populateAdminUser();
+    }
+
+    private void populateRoles() {
+        List<Role> roles = createRoles();
+        roles.forEach((role) -> {
+            boolean roleNotPresent = !roleRepository.findByName(role.getName()).isPresent();
+            if(roleNotPresent)
+                roleRepository.save(role);
+        });
+
+    }
+
+    private List<Role> createRoles() {
+        Role admin = new Role("ROLE_ADMIN");
+        Role user = new Role("ROLE_USER");
+        Role developer = new Role("ROLE_DEVELOPER");
+        return Arrays.asList(admin, user, developer);
+    }
+
+    private void populateAdminUser() {
+        boolean adminNotPresent = !userRepository.findByUsername("admin").isPresent();
+        if(adminNotPresent) {
+            ModUser admin = createAdmin();
+            userRepository.save(admin);
+        }
+    }
+
+    private ModUser createAdmin() {
+        ModUser admin = new ModUser();
+        admin.setUsername("admin");
+        admin.setFullName("Admin");
+        admin.setPassword(passwordEncoder.encode("admin@mod"));
+        HashSet<Role> roleAdmin = new HashSet<>();
+        roleAdmin.add(roleRepository.findByName("ROLE_ADMIN").get());
+        admin.setRoles(roleAdmin);
+        return admin;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/AuthController.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/AuthController.java
new file mode 100644 (file)
index 0000000..6612138
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.controllers;
+
+import org.onap.dcaegen2.platform.mod.exceptions.RoleNotExistsException;
+import org.onap.dcaegen2.platform.mod.exceptions.UserAlreadyExistsException;
+import org.onap.dcaegen2.platform.mod.models.LoginRequest;
+import org.onap.dcaegen2.platform.mod.models.JwtResponse;
+import org.onap.dcaegen2.platform.mod.models.SignupRequest;
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.GenericResponse;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+import org.onap.dcaegen2.platform.mod.repositories.UserRepository;
+import org.onap.dcaegen2.platform.mod.security.jwt.JwtUtils;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+
+import javax.validation.Valid;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Authentication Operations
+ */
+@RestController
+@RequestMapping("/api/auth")
+@CrossOrigin(origins = "*")
+public class AuthController {
+
+    @Autowired
+    AuthenticationManager authenticationManager;
+
+    @Autowired
+    UserRepository userRepository;
+
+    @Autowired
+    RoleRepository roleRepository;
+
+    @Autowired
+    PasswordEncoder passwordEncoder;
+
+    @Autowired
+    JwtUtils jwtUtils;
+
+    @PreAuthorize("isAuthenticated()")
+    @PostMapping("/validate-token")
+    public ResponseEntity<?> validateToken() {
+        return new ResponseEntity("true", HttpStatus.OK);
+    }
+
+    @PostMapping("/signin")
+    public ResponseEntity<?> authenticateUser(@RequestBody @Valid LoginRequest loginRequest) {
+        Authentication authentication = authenticateLoginRequest(loginRequest);
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+        String jwt = jwtUtils.generateJwtToken(authentication);
+        return setUserContext(authentication, jwt);
+    }
+
+    private Authentication authenticateLoginRequest(@RequestBody @Valid LoginRequest loginRequest) {
+        return authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
+        );
+    }
+
+    private ResponseEntity<?> setUserContext(Authentication authentication, String jwt) {
+        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
+        List<String> roles = setRolesFromUserDetails(userDetails);
+        return buildUserContext(jwt, userDetails, roles);
+    }
+
+    private List<String> setRolesFromUserDetails(UserDetailsImpl userDetails) {
+        return userDetails.getAuthorities().stream()
+                .map(item -> ((GrantedAuthority) item).getAuthority())
+                .collect(Collectors.toList());
+    }
+
+    private ResponseEntity<JwtResponse> buildUserContext(String jwt, UserDetailsImpl userDetails, List<String> roles) {
+        return ResponseEntity.ok(JwtResponse.builder()
+                .id(userDetails.getId())
+                .roles(roles)
+                .username(userDetails.getUsername())
+                .token(jwt)
+                .fullName(userDetails.getFullName())
+                .build()
+        );
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/signup")
+    public ResponseEntity<?> registerUser(@RequestBody @Valid SignupRequest request) {
+        checkIfUserExists(request);
+        ModUser user = createNewUser(request);
+        userRepository.save(user);
+        return ResponseEntity.ok(new GenericResponse("User registered successfully!"));
+    }
+
+    private void checkIfUserExists(@RequestBody @Valid SignupRequest signUpRequest) {
+        if (userRepository.existsByUsername(signUpRequest.getUsername()))
+            throw new UserAlreadyExistsException("Username already exists!");
+    }
+
+    private ModUser createNewUser(@RequestBody @Valid SignupRequest request) {
+        ModUser user = new ModUser();
+        user.setUsername(request.getUsername());
+        user.setFullName(request.getFullName());
+        user.setPassword(getEncodedPassword(request));
+        Set<Role> roles = createRoles(request.getRoles());
+        user.setRoles(roles);
+        return user;
+    }
+
+    private String getEncodedPassword(@RequestBody @Valid SignupRequest request) {
+        return passwordEncoder.encode(request.getPassword());
+    }
+
+    public Set<Role> createRoles(Set<String> roleStrings) {
+        Set<Role> roles = new HashSet<>();
+        for (String roleStr : roleStrings) {
+            roles.add(getRole(roleStr));
+        }
+        return roles;
+    }
+
+    private Role getRole(String roleStr) {
+        return roleRepository.findByName(roleStr).orElseThrow(
+                () -> new RoleNotExistsException(String.format("Role %s does not exist", roleStr)));
+    }
+}
+
+
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/RoleController.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/RoleController.java
new file mode 100644 (file)
index 0000000..e129cda
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.controllers;
+
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Role Operations
+ */
+@RestController
+@RequestMapping("/api/roles")
+@CrossOrigin(origins = "*")
+public class RoleController {
+
+    @Autowired
+    RoleRepository roleRepository;
+
+    @GetMapping
+    @ResponseStatus(HttpStatus.OK)
+    public List<String> getRoles(){
+        return roleRepository.findAll()
+                .stream()
+                .map(role -> role.getName())
+                .collect(Collectors.toList());
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/UserController.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/controllers/UserController.java
new file mode 100644 (file)
index 0000000..90e7b62
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.controllers;
+
+import org.onap.dcaegen2.platform.mod.exceptions.UserNotFoundException;
+import org.onap.dcaegen2.platform.mod.models.GenericResponse;
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.UpdateUserRequest;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl;
+import org.onap.dcaegen2.platform.mod.services.MODUserDetailService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+
+/**
+ * @author
+ * @date 09/08/2020
+ * User Operations
+ */
+@RestController
+@RequestMapping("/api/users")
+@CrossOrigin(origins = "*")
+public class UserController {
+
+    @Autowired
+    private MODUserDetailService modUserDetailService;
+
+    @Autowired
+    private UserDetailsServiceImpl userDetailsService;
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/getAll")
+    @ResponseStatus(HttpStatus.OK)
+    public List<ModUser> getAllUsers() {
+        return modUserDetailService.findAll();
+    }
+
+    @PreAuthorize("hasRole('ADMIN') or hasRole('USER')")
+    @GetMapping("/{username}")
+    public UserDetails getUser(@PathVariable String username) {
+        return userDetailsService.loadUserByUsername(username);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PatchMapping("/admin/{username}")
+    public ModUser adminUpdateUserProfile(@PathVariable String username, @RequestBody @Valid UpdateUserRequest
+            userRequest, @RequestHeader (name="Authorization") String token) {
+        return userDetailsService.adminUpdateUser(username, userRequest, token);
+    }
+
+    @PreAuthorize("hasRole('USER') or hasRole('DEVELOPER')")
+    @PatchMapping("/user/{username}")
+    public ModUser userUpdateOwnProfile(@PathVariable String username, @RequestBody @Valid UpdateUserRequest
+            userRequest, @RequestHeader (name="Authorization") String token) {
+        return userDetailsService.userUpdateOwnProfile(username, userRequest, token);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @DeleteMapping("/{username}")
+    public ResponseEntity<?> deleteUser(@PathVariable String username) {
+        modUserDetailService.deleteUserByUsername(username);
+        return ResponseEntity.ok(new GenericResponse("User " + username + " was removed"));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    public void userNotFoundHandler(UserNotFoundException ex) {
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/AppExceptionHandler.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/AppExceptionHandler.java
new file mode 100644 (file)
index 0000000..e029e69
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.exceptions;
+
+import org.onap.dcaegen2.platform.mod.models.GenericResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Exception handler for the Auth Service Application
+ */
+@ControllerAdvice
+@Slf4j
+public class AppExceptionHandler {
+
+    @ExceptionHandler
+    public ResponseEntity<GenericResponse> resolveRuntimeException(UserAlreadyExistsException ex){
+        log.error(ex.getMessage());
+        return new ResponseEntity<>(new GenericResponse(ex.getMessage()), HttpStatus.CONFLICT);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<GenericResponse> resolveRoleNotFoundException(RoleNotExistsException ex){
+        log.error(ex.getMessage());
+        return new ResponseEntity<>(new GenericResponse(ex.getMessage()), HttpStatus.BAD_REQUEST);
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/IllegalUserOperationException.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/IllegalUserOperationException.java
new file mode 100644 (file)
index 0000000..4a5837a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.exceptions;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Exception for Illegal Operation
+ */
+public class IllegalUserOperationException extends RuntimeException {
+    public IllegalUserOperationException(String s) {
+        super(s);
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/RoleNotExistsException.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/RoleNotExistsException.java
new file mode 100644 (file)
index 0000000..dfa6efb
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.exceptions;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Exception for Role not exist
+ */
+public class RoleNotExistsException extends RuntimeException {
+    public RoleNotExistsException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/UserAlreadyExistsException.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/UserAlreadyExistsException.java
new file mode 100644 (file)
index 0000000..475dc44
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.exceptions;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Exception for User already exists
+ */
+public class UserAlreadyExistsException extends RuntimeException {
+    public UserAlreadyExistsException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/UserNotFoundException.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/exceptions/UserNotFoundException.java
new file mode 100644 (file)
index 0000000..95c6a70
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.exceptions;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Exception for User Not Found
+ */
+public class UserNotFoundException extends RuntimeException {
+    public UserNotFoundException(String s) {
+        super(s);
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/GenericResponse.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/GenericResponse.java
new file mode 100644 (file)
index 0000000..c4625a4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Generic Response for the Auth Service Request
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+public class GenericResponse {
+
+    private String message;
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/JwtResponse.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/JwtResponse.java
new file mode 100644 (file)
index 0000000..8855245
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * JWT Response for the Auth Service Request
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class JwtResponse {
+
+    private String token;
+    private String type = "Bearer";
+    private String id;
+    private String username;
+    private String fullName;
+    private List<String> roles;
+
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/LoginRequest.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/LoginRequest.java
new file mode 100644 (file)
index 0000000..af0f710
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Login request
+ */
+@Data
+public class LoginRequest {
+
+    @NotBlank
+    private String username;
+
+    @NotBlank
+    private String password;
+
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/ModUser.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/ModUser.java
new file mode 100644 (file)
index 0000000..4da5c98
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Assigning Roles to User
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Document(collection = "users")
+public class ModUser implements Serializable {
+
+    @Id
+    public String _id;
+
+    @NotBlank
+    @Size(max = 10)
+    private String username;
+
+    @NotBlank
+    private String password;
+
+    @NotBlank
+    private String fullName;
+
+    @DBRef
+    private Set<Role> roles = new HashSet<>();
+
+    public ModUser(@NotBlank @Size(max = 10) String username, @NotBlank String password, @NotBlank String fullName) {
+        this.username = username;
+        this.password = password;
+        this.fullName=fullName;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/Role.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/Role.java
new file mode 100644 (file)
index 0000000..8cce344
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Roles
+ */
+@Document(collection = "roles")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Role {
+
+    @Id
+    private String id;
+
+    private String name;
+
+    public Role(String name) {
+        this.name = name;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/SignupRequest.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/SignupRequest.java
new file mode 100644 (file)
index 0000000..f705d76
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.Set;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * To validate User enter Details
+ */
+@Data
+public class SignupRequest {
+
+    @NotBlank(message = "username must not be blank")
+    @Size(min = 5, max = 10, message = "username must be between 5 and 10 characters.")
+    private String username;
+
+    @NotNull(message = "username must not be null")
+    @Size(min = 1, message = "At least 1 role must be passed")
+    private Set<String> roles;
+
+    @NotBlank(message = "password must not be blank")
+    private String password;
+
+    @NotBlank(message = "Full name must not be blank")
+    private String fullName;
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/UpdateUserRequest.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/models/UpdateUserRequest.java
new file mode 100644 (file)
index 0000000..6e3abf6
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.models;
+
+import lombok.Data;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.util.Set;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * To update User Details of Name and Password
+ */
+@Data
+public class UpdateUserRequest {
+
+    @Size(min = 1, message = "At least 1 role must be passed")
+    private Set<String> roles;
+
+    @Pattern(regexp = "^(?!\\s*$).+", message = "password must not be blank")
+    private String password;
+
+    @Pattern(regexp = "^(?!\\s*$).+", message = "fullName must not be blank")
+    private String fullName;
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/repositories/RoleRepository.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/repositories/RoleRepository.java
new file mode 100644 (file)
index 0000000..6efad17
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.repositories;
+
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.Optional;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Interface to find user exists or not
+ */
+public interface RoleRepository extends MongoRepository<Role, String> {
+    Optional<Role> findByName(String name);
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/repositories/UserRepository.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/repositories/UserRepository.java
new file mode 100644 (file)
index 0000000..442ebf1
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.repositories;
+
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.springframework.data.mongodb.repository.MongoRepository;
+
+import java.util.Optional;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Interface to Find/Valid/Delete user
+ */
+public interface UserRepository extends MongoRepository<ModUser, String>{
+    Optional<ModUser> findByUsername(String username);
+    Boolean existsByUsername(String username);
+    void deleteByUsername(String username);
+}
+
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/WebSecurityConfigurer.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/WebSecurityConfigurer.java
new file mode 100644 (file)
index 0000000..cfccce4
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.security;
+
+import org.onap.dcaegen2.platform.mod.security.jwt.AuthEntryPointJwt;
+import org.onap.dcaegen2.platform.mod.security.jwt.AuthTokenFilter;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Allows customization to the Spring WebSecurity
+ */
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
+
+    @Autowired
+    UserDetailsServiceImpl userDetailsService;
+
+    @Autowired
+    private AuthEntryPointJwt unauthorizedHandler;
+
+    @Bean
+    public AuthTokenFilter authenticationJwtTokenFilter(){
+        return new AuthTokenFilter();
+    }
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
+        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
+    }
+
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception{
+        return super.authenticationManagerBean();
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.cors().and().csrf().disable()
+                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+                .authorizeRequests().antMatchers("/api/auth/**").permitAll()
+                .antMatchers("/api/users/**").permitAll()
+                .anyRequest().authenticated();
+
+        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthEntryPointJwt.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthEntryPointJwt.java
new file mode 100644 (file)
index 0000000..310d487
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.security.jwt;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * JWT Authentication Entry Point
+ */
+
+@Slf4j
+@Component
+public class AuthEntryPointJwt implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response,
+                         AuthenticationException authException) throws IOException, ServletException {
+        log.error("Unauthorized error: {}", authException.getMessage());
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthTokenFilter.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/AuthTokenFilter.java
new file mode 100644 (file)
index 0000000..012c333
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.security.jwt;
+
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * Authentication Token Filter
+ */
+@Slf4j
+public class AuthTokenFilter extends OncePerRequestFilter {
+
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    @Autowired
+    private UserDetailsServiceImpl userDetailsService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+        try{
+            String jwt = parseJwt(httpServletRequest);
+            if (jwt != null && jwtUtils.validateJwtToken(jwt)){
+                String username = jwtUtils.getUserNameFromJwtToken(jwt);
+                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+                UsernamePasswordAuthenticationToken authenticationToken =
+                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
+                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+            }
+        }catch (Exception e){
+            logger.error("Cannot set user authentication: {}", e);
+        }
+        filterChain.doFilter(httpServletRequest, httpServletResponse);
+    }
+
+    private String parseJwt(HttpServletRequest httpServletRequest) {
+        String headerAuth = httpServletRequest.getHeader("Authorization");
+
+        if(StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")){
+            return headerAuth.substring(7, headerAuth.length());
+        }
+        return null;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/JwtUtils.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/jwt/JwtUtils.java
new file mode 100644 (file)
index 0000000..3b6d311
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.security.jwt;
+
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsImpl;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.UnsupportedJwtException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * JWT Utils
+ */
+@Slf4j
+@Component
+public class JwtUtils {
+
+    @Value("${mod-portal.jwt.secret}")
+    private String jwtSecret;
+
+    @Value("${mod-portal.jwt.jwtExpirationMs}")
+    private int jwtExpirationMs;
+
+    public String generateJwtToken(Authentication authentication) {
+
+        UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
+
+        return Jwts.builder()
+                .setSubject((userPrincipal.getUsername()))
+                .claim("roles", userPrincipal.getAuthoritiesAsList())
+                .claim("fullName", userPrincipal.getFullName())
+                .setIssuedAt(new Date())
+                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
+                .signWith(SignatureAlgorithm.HS512, jwtSecret)
+                .compact();
+    }
+
+    public String getUserNameFromJwtToken(String token) {
+        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
+    }
+
+    public boolean validateJwtToken(String authToken) {
+        try {
+            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
+            return true;
+        } catch (SignatureException e) {
+            log.error("Invalid JWT signature: {}", e.getMessage());
+        } catch (MalformedJwtException e) {
+            log.error("Invalid JWT token: {}", e.getMessage());
+        } catch (ExpiredJwtException e) {
+            log.error("JWT token is expired: {}", e.getMessage());
+        } catch (UnsupportedJwtException e) {
+            log.error("JWT token is unsupported: {}", e.getMessage());
+        } catch (IllegalArgumentException e) {
+            log.error("JWT claims string is empty: {}", e.getMessage());
+        }
+
+        return false;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsImpl.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsImpl.java
new file mode 100644 (file)
index 0000000..82cb577
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.security.services;
+
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.EqualsAndHashCode;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * User Details Implementation
+ */
+@EqualsAndHashCode
+public class UserDetailsImpl implements UserDetails{
+
+    private static final long serialVersionUID = 1L;
+
+    private String id;
+
+    private String username;
+
+    private String fullName;
+
+    @JsonIgnore
+    private String password;
+
+    private Collection<? extends GrantedAuthority> authorities;
+
+    public UserDetailsImpl(String id, String username, String fullName, String password, Collection<?
+            extends GrantedAuthority> authorities) {
+        this.id = id;
+        this.username = username;
+        this.fullName = fullName;
+        this.password = password;
+        this.authorities = authorities;
+    }
+
+    public static UserDetails build(ModUser user) {
+        List<GrantedAuthority> authorities = user.getRoles().stream()
+                .map(role -> new SimpleGrantedAuthority(role.getName()))
+                .collect(Collectors.toList());
+
+        return new UserDetailsImpl(
+                user.get_id(),
+                user.getUsername(),
+                user.getFullName(),
+                user.getPassword(),
+                authorities
+        );
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return authorities;
+    }
+
+    public List<String> getAuthoritiesAsList(){
+        return  authorities.stream().map(GrantedAuthority::getAuthority)
+                    .collect(Collectors.toList());
+    }
+
+    @Override
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    public String getFullName() {
+        return fullName;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsServiceImpl.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/security/services/UserDetailsServiceImpl.java
new file mode 100644 (file)
index 0000000..5c13e11
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.security.services;
+
+import org.onap.dcaegen2.platform.mod.controllers.AuthController;
+import org.onap.dcaegen2.platform.mod.exceptions.UserNotFoundException;
+import org.onap.dcaegen2.platform.mod.exceptions.IllegalUserOperationException;
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.models.UpdateUserRequest;
+import org.onap.dcaegen2.platform.mod.repositories.UserRepository;
+import org.onap.dcaegen2.platform.mod.security.jwt.JwtUtils;
+import lombok.Setter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.Set;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * User Details Service
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+    @Autowired
+    @Setter
+    UserRepository userRepository;
+
+    @Autowired
+    PasswordEncoder passwordEncoder;
+
+    @Autowired
+    AuthController authController;
+
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        ModUser user = userRepository.findByUsername(username)
+                .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
+
+        return UserDetailsImpl.build(user);
+    }
+
+    public ModUser adminUpdateUser(String username, UpdateUserRequest userRequest, String token) {
+        return updateUserProfile(username, userRequest);
+    }
+
+    public ModUser userUpdateOwnProfile(String username, UpdateUserRequest userRequest, String token) {
+        String usernameFromToken = jwtUtils.getUserNameFromJwtToken(token.substring(7));
+        if (usernameFromToken.equals(username)) {
+            return updateUserProfile(username, userRequest);
+        } else {
+            throw new IllegalUserOperationException("Permission denied to update user profile of " + username);
+        }
+    }
+
+    private ModUser updateUserProfile(String username, UpdateUserRequest userRequest) {
+        ModUser modUser = userRepository.findByUsername(username).orElseThrow(() -> new UserNotFoundException(String.format("User %s not found", username)));
+        modUser = updateUserFields(modUser, userRequest);
+        return userRepository.save(modUser);
+    }
+
+    private ModUser updateUserFields(ModUser modUser, UpdateUserRequest userRequest) {
+        if (userRequest.getFullName() != null) modUser.setFullName(userRequest.getFullName());
+        if (userRequest.getPassword() != null) modUser.setPassword(passwordEncoder.encode(userRequest.getPassword()));
+        if (userRequest.getRoles() != null) {
+            Set<Role> roles = authController.createRoles(userRequest.getRoles());
+            modUser.setRoles(roles);
+        }
+        return modUser;
+    }
+}
diff --git a/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/services/MODUserDetailService.java b/mod2/auth-service/src/main/java/org/onap/dcaegen2/platform/mod/services/MODUserDetailService.java
new file mode 100644 (file)
index 0000000..a6a6de5
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.services;
+
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.repositories.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 09/08/2020
+ * This Service provides APIs to manage User Entity
+ */
+
+@Service
+public class MODUserDetailService {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    public MODUserDetailService(UserRepository userRepository) {
+        this.userRepository = userRepository;
+    }
+
+    public List<ModUser> findAll() {
+        return userRepository.findAll();
+    }
+
+    public void deleteUserByUsername(String username) {
+        userRepository.deleteByUsername(username);
+    }
+
+}
diff --git a/mod2/auth-service/src/main/resources/application.properties b/mod2/auth-service/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..f3f5d37
--- /dev/null
@@ -0,0 +1,32 @@
+#
+# /*
+#  * ============LICENSE_START=======================================================
+#  *  org.onap.dcae
+#  *  ================================================================================
+#  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+#  */
+#
+
+server.port=8082
+
+#spring.data.mongodb.host=mongo_db
+spring.data.mongodb.host=localhost
+#spring.data.mongodb.host=zlecdyh2bdcc1s2dokr05.ec53e7.dyh2b.tci.att.com
+spring.data.mongodb.port=27017
+spring.data.mongodb.database=dcae_mod
+
+mod-portal.jwt.secret=mod2020!
+mod-portal.jwt.jwtExpirationMs=43200000
\ No newline at end of file
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/AuthObjectMother.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/AuthObjectMother.java
new file mode 100644 (file)
index 0000000..3a22af3
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.onap.dcaegen2.platform.mod.models.LoginRequest;
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.models.SignupRequest;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsImpl;
+import org.onap.dcaegen2.platform.mod.util.TestUtil;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * Mock for AuthenticationController Test Case
+ */
+
+public class AuthObjectMother {
+    public static final String LOGIN_REQUEST = "src/test/resources/http/requests/AuthLoginRequest.json";
+    public static final String SIGNUP_REQUEST = "src/test/resources/http/requests/AuthSignupRequest.json";
+
+    public static String asJsonString(final Object object) {
+        try {
+            return new ObjectMapper().writeValueAsString(object);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static LoginRequest getLoginRequest() {
+        return TestUtil.deserializeJsonFileToModel(LOGIN_REQUEST, LoginRequest.class);
+    }
+
+    public static SignupRequest getSignupRequest() {
+        return TestUtil.deserializeJsonFileToModel(SIGNUP_REQUEST, SignupRequest.class);
+    }
+
+    public static UserDetailsImpl getUserDetailsImpl() {
+        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN");
+        List authorities = new ArrayList();
+        authorities.add(simpleGrantedAuthority);
+        return new UserDetailsImpl("admin123", "admin123", "admin123", "admin123", authorities);
+    }
+
+    public static ModUser getModUser() {
+        ModUser user = new ModUser();
+        user.setUsername("test");
+        user.setFullName("test");
+        user.setPassword("password");
+        Set<Role> roles = new HashSet<>();
+        Role role = new Role();
+        role.setName("test");
+        role.setId("123");
+        roles.add(role);
+        user.setRoles(roles);
+        return user;
+    }
+}
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/RoleObjectMother.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/RoleObjectMother.java
new file mode 100644 (file)
index 0000000..68002ac
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * Mock for RoleController Test Case
+ */
+public class RoleObjectMother {
+
+    public static List<Role> getRoles() {
+        List<Role> roles = new ArrayList();
+        Role role1 = new Role();
+        role1.setId("123");
+        role1.setName("Admin123");
+
+        Role role2 = new Role();
+        role2.setId("1234");
+        role2.setName("Admin1234");
+
+        roles.add(role1);
+        roles.add(role2);
+
+        return roles;
+    }
+
+}
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/UserObjectMother.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/UserObjectMother.java
new file mode 100644 (file)
index 0000000..301b81f
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.models.UpdateUserRequest;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsImpl;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * Mock for UserController Test Case
+ */
+public class UserObjectMother {
+
+    public static final String userId = "admin";
+
+    public static List<ModUser> getUsers() {
+        List<ModUser> users = new ArrayList();
+        ModUser modUser1 = new ModUser();
+        modUser1.set_id("123");
+        modUser1.setFullName("Admin123");
+        modUser1.setPassword("Admin123");
+        Set<Role> roles = new HashSet<>();
+        Role role = new Role();
+        role.setName("test");
+        role.setId("123");
+        roles.add(role);
+        modUser1.setRoles(roles);
+
+        ModUser modUser2 = new ModUser();
+        modUser2.set_id("1234");
+        modUser2.setFullName("Admin1234");
+        modUser2.setPassword("Admin1234");
+        Set<Role> roles1 = new HashSet<>();
+        Role role1 = new Role();
+        role.setName("test1");
+        role.setId("1234");
+        roles.add(role1);
+        modUser2.setRoles(roles1);
+
+        users.add(modUser1);
+        users.add(modUser2);
+
+        return users;
+    }
+
+    public static ModUser getModUser() {
+        ModUser user = new ModUser();
+        user.setUsername("test");
+        user.setFullName("test");
+        user.setPassword("password");
+        Set<Role> roles = new HashSet<>();
+        Role role = new Role();
+        role.setName("test");
+        role.setId("123");
+        roles.add(role);
+        user.setRoles(roles);
+        return user;
+    }
+
+    public static UpdateUserRequest getUpdateUserRequest(){
+        UpdateUserRequest user = new UpdateUserRequest();
+        user.setFullName("test");
+        user.setPassword("password");
+        Set<String> roles = new HashSet<>();
+        roles.add("ROLE_PST");
+        user.setRoles(roles);
+        return user;
+    }
+
+}
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/util/TestUtil.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/util/TestUtil.java
new file mode 100644 (file)
index 0000000..111653f
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.util;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * TestUtils for test cases
+ */
+public class TestUtil {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private TestUtil() {}
+
+    public static Map<String, Object> readJsonFileAsObjectMap(String filePath) {
+        try {
+            return MAPPER.readValue(new File(filePath), new TypeReference<Map<String, Object>>() {});
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+
+    public static <T> T deserializeJsonFileToModel(String filePath, Class<T> modelClass) {
+        try {
+            return MAPPER.readValue(new File(filePath), modelClass);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+}
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/AuthControllerTest.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/AuthControllerTest.java
new file mode 100644 (file)
index 0000000..2aad289
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.onap.dcaegen2.platform.mod.controllers.AuthController;
+import org.onap.dcaegen2.platform.mod.models.*;
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+import org.onap.dcaegen2.platform.mod.repositories.UserRepository;
+import org.onap.dcaegen2.platform.mod.security.jwt.AuthEntryPointJwt;
+import org.onap.dcaegen2.platform.mod.security.jwt.JwtUtils;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.*;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.client.RestTemplate;
+import org.testng.annotations.BeforeTest;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.mockito.Mockito.*;
+import static org.onap.dcaegen2.platform.mod.objectmothers.AuthObjectMother.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * Mock Test cases for AuthenticationController
+ */
+@WebMvcTest(AuthController.class)
+public class AuthControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    JwtUtils jwtUtils;
+
+    @MockBean
+    AuthenticationManager authenticationManager;
+
+    @MockBean
+    UserRepository userRepository;
+
+    @MockBean
+    RoleRepository roleRepository;
+
+    @MockBean
+    PasswordEncoder passwordEncoder;
+
+    @MockBean
+    UserDetailsServiceImpl userDetailsService;
+
+    @MockBean
+    AuthEntryPointJwt authEntryPointJwt;
+
+    @Mock
+    Authentication authentication;
+
+    @BeforeEach
+    void setUp() {
+    }
+
+
+    @Test
+    void test_signin_returnsSuccessResponse() throws Exception {
+
+        LoginRequest loginRequest = getLoginRequest();
+
+        when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()))).thenReturn(authentication);
+        when(authentication.getPrincipal()).thenReturn(getUserDetailsImpl());
+        when(jwtUtils.generateJwtToken(any())).thenReturn("Demo");
+
+        mockMvc.perform(post("/api/auth/signin")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(asJsonString(loginRequest)).accept(MediaType.APPLICATION_JSON))
+                .andExpect(jsonPath("$.token", notNullValue()))
+                .andExpect(status().isOk());
+
+        verify(authenticationManager, times(1)).authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
+        verify(jwtUtils, times(1)).generateJwtToken(any());
+    }
+
+
+    @WithMockUser(roles="ADMIN")
+    @Test
+    void test_signup_returnsSuccessResponse() throws Exception {
+
+        SignupRequest signUpRequest = getSignupRequest();
+
+        when(userRepository.existsByUsername(signUpRequest.getUsername())).thenReturn(false);
+        when(passwordEncoder.encode(signUpRequest.getPassword())).thenReturn("password");
+        when(roleRepository.findByName(anyString())).thenReturn(Optional.of(new Role()));
+        when(userRepository.save(any())).thenReturn(getModUser());
+
+        mockMvc.perform(post("/api/auth/signup")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(asJsonString(signUpRequest)).accept(MediaType.APPLICATION_JSON))
+                .andExpect(jsonPath("$.message", notNullValue()))
+                .andExpect(status().isOk());
+
+        verify(userRepository, times(1)).existsByUsername(signUpRequest.getUsername());
+        verify(passwordEncoder, times(1)).encode(signUpRequest.getPassword());
+        verify(roleRepository, times(1)).findByName(anyString());
+        verify(userRepository, times(1)).save(any());
+    }
+
+
+}
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/RoleControllerTest.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/RoleControllerTest.java
new file mode 100644 (file)
index 0000000..48b5d4b
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.onap.dcaegen2.platform.mod.controllers.RoleController;
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.Role;
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+import org.onap.dcaegen2.platform.mod.security.jwt.AuthEntryPointJwt;
+import org.onap.dcaegen2.platform.mod.security.jwt.JwtUtils;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.onap.dcaegen2.platform.mod.objectmothers.AuthObjectMother.asJsonString;
+import static org.onap.dcaegen2.platform.mod.objectmothers.RoleObjectMother.getRoles;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * Mock Test cases for RoleController
+ */
+@WebMvcTest(RoleController.class)
+public class RoleControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    RoleRepository roleRepository;
+
+    @MockBean
+    UserDetailsServiceImpl userDetailsService;
+
+    @MockBean
+    AuthEntryPointJwt authEntryPointJwt;
+
+    @MockBean
+    JwtUtils jwtUtils;
+
+    @Mock
+    Authentication authentication;
+
+    @BeforeEach
+    void setUp() {
+    }
+
+
+    @Test
+    void test_getRoles() throws Exception {
+
+        Mockito.when(roleRepository.findAll()).thenReturn(getRoles());
+
+        MvcResult result =  mockMvc.perform(get("/api/roles")
+            .contentType(MediaType.APPLICATION_JSON))
+            .andExpect(status().isOk()).andReturn();
+
+        Assert.assertNotNull(result.getResponse().getContentAsString());
+
+    }
+
+}
diff --git a/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/UserControllerTest.java b/mod2/auth-service/src/test/java/org/onap/dcaegen2/platform/mod/web/UserControllerTest.java
new file mode 100644 (file)
index 0000000..1374e0e
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ *
+ *  * ============LICENSE_START=======================================================
+ *  *  org.onap.dcae
+ *  *  ================================================================================
+ *  *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ *
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.apache.tools.ant.taskdefs.optional.extension.Specification;
+import org.junit.Assert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.Request;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.onap.dcaegen2.platform.mod.controllers.RoleController;
+import org.onap.dcaegen2.platform.mod.controllers.UserController;
+import org.onap.dcaegen2.platform.mod.models.LoginRequest;
+import org.onap.dcaegen2.platform.mod.models.ModUser;
+import org.onap.dcaegen2.platform.mod.models.UpdateUserRequest;
+import org.onap.dcaegen2.platform.mod.repositories.RoleRepository;
+import org.onap.dcaegen2.platform.mod.repositories.UserRepository;
+import org.onap.dcaegen2.platform.mod.security.jwt.AuthEntryPointJwt;
+import org.onap.dcaegen2.platform.mod.security.jwt.JwtUtils;
+import org.onap.dcaegen2.platform.mod.security.services.UserDetailsServiceImpl;
+import org.onap.dcaegen2.platform.mod.services.MODUserDetailService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.client.RequestMatcher;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultMatcher;
+
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+import static org.onap.dcaegen2.platform.mod.objectmothers.AuthObjectMother.asJsonString;
+import static org.onap.dcaegen2.platform.mod.objectmothers.AuthObjectMother.getModUser;
+import static org.onap.dcaegen2.platform.mod.objectmothers.RoleObjectMother.getRoles;
+import static org.onap.dcaegen2.platform.mod.objectmothers.UserObjectMother.*;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * @author
+ * @date 09/22/2020
+ * Mock Test cases for UserController
+ */
+@WebMvcTest(UserController.class)
+public class UserControllerTest {
+
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    UserDetailsServiceImpl userDetailsService;
+
+    @MockBean
+    UserRepository userRepository;
+
+    @MockBean
+    MODUserDetailService modUserDetailService;
+
+    @MockBean
+    RoleRepository roleRepository;
+
+    @MockBean
+    AuthEntryPointJwt authEntryPointJwt;
+
+    @MockBean
+    JwtUtils jwtUtils;
+
+    @Mock
+    Authentication authentication;
+
+
+    @BeforeEach
+    void setUp() {
+    }
+
+    @WithMockUser(roles="ADMIN")
+    @Test
+    void test_getUsername() throws Exception {
+
+        when(userRepository.findByUsername(any())).thenReturn(Optional.of(new ModUser()));
+
+        MvcResult result =  mockMvc.perform(get("/api/users/" + userId)
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isOk()).andReturn();
+
+        Assert.assertNotNull(result.getResponse().getContentAsString());
+        verify(userRepository, times(1)).findByUsername(any());
+    }
+
+    @Test
+    void test_getAllUsers() throws Exception {
+
+        when(modUserDetailService.findAll()).thenReturn(getUsers());
+
+        MvcResult result =  mockMvc.perform(get("/api/users/getAll")
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isOk()).andReturn();
+
+        Assert.assertNotNull(result.getResponse().getContentAsString());
+        verify(modUserDetailService, times(1)).findAll();
+    }
+
+
+    @WithMockUser(roles="ADMIN")
+    @Test
+    void test_deleteUser() throws Exception {
+
+        doNothing().when(modUserDetailService).deleteUserByUsername(any(String.class));
+
+        MvcResult result =  mockMvc.perform(delete("/api/users/" + userId)
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isOk()).andReturn();
+
+        Assert.assertNotNull(result.getResponse().getContentAsString());
+        verify(modUserDetailService, times(1)).deleteUserByUsername(any(String.class));
+    }
+
+
+    @WithMockUser(username="ADMIN")
+    @Test
+    void test_userUpdateOwnProfile_returnsSuccessResponse() throws Exception {
+        //arrange
+        UpdateUserRequest updateUserRequest = getUpdateUserRequest();
+
+        when(userDetailsService.adminUpdateUser(userId,updateUserRequest,"token")).thenReturn(getModUser());
+
+        mockMvc.perform(patch("/api/users/admin/" + userId)
+                //.header("Authorization", "token")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(asJsonString(updateUserRequest)).accept(MediaType.APPLICATION_JSON))
+                //.andExpect(jsonPath("$.message", notNullValue()))
+                .andExpect(status().isOk()).andReturn();
+
+        verify(userDetailsService, times(1)).adminUpdateUser(anyString(),updateUserRequest,anyString());
+    }
+
+
+}
\ No newline at end of file
diff --git a/mod2/auth-service/src/test/resources/application.properties b/mod2/auth-service/src/test/resources/application.properties
new file mode 100644 (file)
index 0000000..d6a913c
--- /dev/null
@@ -0,0 +1 @@
+#spring.data.mongodb.port=0
\ No newline at end of file
diff --git a/mod2/auth-service/src/test/resources/http/requests/AuthLoginRequest.json b/mod2/auth-service/src/test/resources/http/requests/AuthLoginRequest.json
new file mode 100644 (file)
index 0000000..fbdc4a9
--- /dev/null
@@ -0,0 +1,4 @@
+{
+       "username": "admin",
+       "password": "admin@mod"
+}
diff --git a/mod2/auth-service/src/test/resources/http/requests/AuthSignupRequest.json b/mod2/auth-service/src/test/resources/http/requests/AuthSignupRequest.json
new file mode 100644 (file)
index 0000000..25be086
--- /dev/null
@@ -0,0 +1,6 @@
+{
+       "username": "admin1",
+       "fullName": "Administrator",
+       "password": "admin@mod",
+       "roles": ["ROLE_PST"]
+}
\ No newline at end of file
diff --git a/mod2/auth-service/src/test/resources/specification/componentSpec_hello_world-with-dmaap.json b/mod2/auth-service/src/test/resources/specification/componentSpec_hello_world-with-dmaap.json
new file mode 100644 (file)
index 0000000..0ad6b6e
--- /dev/null
@@ -0,0 +1,149 @@
+{
+       "self": {
+               "component_type": "docker",
+               "description": "Hello World mS for subscribing the data from local DMaaP, DR or MR, processing them and publishing them as PM files to local DMaaP DR",
+               "name": "dcae-collectors-vcc-helloworld-pm",
+               "version": "1.0.1"
+       },
+
+       "services": {
+               "calls": [],
+               "provides": []
+       },
+
+       "streams": {
+               "publishes": [{
+                       "config_key": "DCAE-HELLO-WORLD-PUB-DR",
+                       "format": "dataformat_Hello_World_PM",
+                       "type": "data_router",
+                       "version": "1.0.0"
+               },
+                       {
+                               "config_key": "DCAE-HELLO-WORLD-PUB-MR",
+                               "format": "dataformat_Hello_World_PM",
+                               "type": "message_router",
+                               "version": "1.0.0"
+                       }
+               ],
+
+               "subscribes": [{
+                       "config_key": "DCAE-HELLO-WORLD-SUB-MR",
+                       "format": "dataformat_Hello_World_PM",
+                       "route": "/DCAE_HELLO_WORLD_SUB_MR",
+                       "type": "message_router",
+                       "version": "1.0.0"
+               },
+                       {
+                               "config_key": "DCAE-HELLO-WORLD-SUB-DR",
+                               "format": "dataformat_Hello_World_PM",
+                               "route": "/DCAE-HELLO-WORLD-SUB-DR",
+                               "type": "data_router",
+                               "version": "1.0.0"
+                       }
+               ]
+       },
+
+       "parameters":
+       [
+               {
+                       "name": "vcc_hello_name",
+                       "value": "120",
+                       "type": "integer",
+                       "description": "the name entered for specific person",
+                       "sourced_at_deployment": false,
+                       "designer_editable": false,
+                       "policy_editable": false
+               },
+
+               {
+                       "name": "useDtiConfig",
+                       "value": false,
+                       "type" : "boolean",
+                       "description": "component depends on configuration from dti.",
+                       "sourced_at_deployment": false,
+                       "designer_editable": true,
+                       "policy_editable": false,
+                       "required" : true
+               },
+
+               {
+                       "name": "isSelfServeComponent",
+                       "value": false,
+                       "type": "boolean",
+                       "description": "Is this used as self serve component.",
+                       "sourced_at_deployment": false,
+                       "designer_editable": true,
+                       "policy_editable": false,
+                       "required" : true
+               }
+       ],
+
+       "auxilary": {
+               "healthcheck": {
+                       "interval": "60s",
+                       "initialDelaySeconds": "120s",
+                       "timeout": "20s",
+                       "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+                       "type": "docker"
+               },
+               "livehealthcheck": {
+                       "interval": "60s",
+                       "initialDelaySeconds": "120s",
+                       "timeout": "20s",
+                       "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+                       "type": "docker"
+               },
+               "reconfigs":{
+                       "app_reconfig" : "abc"
+               },
+
+               "volumes": [
+                       {
+                               "container": {
+                                       "bind": "/opt/app/dcae-certificate"
+                               },
+                               "host": {
+                                       "path": "/opt/app/dcae-certificate"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/logs/DCAE/dmd/AGENT"
+                               },
+                               "host": {
+                                       "path": "/opt/logs/DCAE/helloworldpm/dmd/AGENT"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/logs/DCAE/dmd/WATCHER"
+                               },
+                               "host": {
+                                       "path": "/opt/logs/DCAE/helloworldpm/dmd/WATCHER"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/app/vcc/logs/DCAE"
+                               },
+                               "host": {
+                                       "path": "/opt/logs/DCAE/helloworldpm/vcc-logs"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/app/vcc/archive/data"
+                               },
+                               "host": {
+                                       "path": "/opt/data/DCAE/helloworldpm/vcc-archive"
+                               }
+                       }
+
+               ]
+
+       },
+       "artifacts": [{
+               "type": "docker image",
+               "uri": "dockercentral.it.att.com:5100/com.att.sample/dcae-controller-vcc-helloworld-pm:18.02-001"
+       }]
+}
\ No newline at end of file
diff --git a/mod2/auth-service/src/test/resources/specification/policy_json_sample_3.json b/mod2/auth-service/src/test/resources/specification/policy_json_sample_3.json
new file mode 100644 (file)
index 0000000..4a8c76f
--- /dev/null
@@ -0,0 +1,28 @@
+{
+       "policies": [
+               {
+                       "configAttributes": "",
+               "configName": "",
+               "onapName": "DCAE",
+               "policyName": "DCAE.Config_",
+               "unique": false
+               },
+               {
+                       "onapName": "DCAE",
+                       "policyName": "DCAE.Config_",
+                       "unique": true
+               },
+               {
+                       "configAttributes": "",
+                       "configName": "",
+                       "onapName": "DCAE",
+                       "policyName": "DCAE.Config_*",
+                       "unique": false
+               }
+       ],
+       "policy": [
+               {
+                       "policy_id" : "id_0"
+               }
+       ]
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/Dockerfile b/mod2/catalog-service/Dockerfile
new file mode 100644 (file)
index 0000000..0870a21
--- /dev/null
@@ -0,0 +1,14 @@
+FROM onap/integration-java11:7.1.0
+
+ARG PROJECT_BUILD_DIR_NAME
+ARG FINAL_JAR
+
+WORKDIR /usr/app
+VOLUME /tmp
+
+COPY ${PROJECT_BUILD_DIR_NAME}/${FINAL_JAR} .
+
+EXPOSE 8080
+ENTRYPOINT ["java", \
+            "-Djava.security.egd=file:/dev/./urandom", \
+            "-jar", "mod-catalog-service.jar"]
diff --git a/mod2/catalog-service/lombok.config b/mod2/catalog-service/lombok.config
new file mode 100644 (file)
index 0000000..8f7e8aa
--- /dev/null
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/mod2/catalog-service/pom.xml b/mod2/catalog-service/pom.xml
new file mode 100644 (file)
index 0000000..78cced4
--- /dev/null
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ ============LICENSE_START=======================================================
+  ~  org.onap.dcae
+  ~  ================================================================================
+  ~  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.oparent</groupId>
+        <artifactId>oparent</artifactId>
+        <version>2.0.0</version>
+    </parent>
+    <groupId>org.onap.dcaegen2.platform.mod</groupId>
+    <artifactId>catalog-service</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <properties>
+        <java.version>11</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <spring-boot.version>2.2.5.RELEASE</spring-boot.version>
+        <dockerfile-maven-plugin.version>1.4.1</dockerfile-maven-plugin.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.9.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>2.9.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <version>4.0.1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <!-- Import dependency management from Spring Boot -->
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>2.2.5.RELEASE</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>dockerfile-maven-plugin</artifactId>
+                <version>${dockerfile-maven-plugin.version}</version>
+                <configuration>
+                    <contextDirectory>${project.basedir}</contextDirectory>
+                    <repository>onap/${project.groupId}.${project.artifactId}</repository>
+                    <buildArgs>
+                        <PROJECT_BUILD_DIR_NAME>target</PROJECT_BUILD_DIR_NAME>
+                        <FINAL_JAR>${project.build.finalName}.jar</FINAL_JAR>
+                    </buildArgs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>build-image</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>tag-and-push-image-latest</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>tag</goal>
+                            <goal>push</goal>
+                        </goals>
+                        <configuration>
+                            <repository>onap/${project.groupId}.${project.artifactId}</repository>
+                            <tag>latest</tag>
+                            <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>tag-and-push-image-with-version</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>tag</goal>
+                            <goal>push</goal>
+                        </goals>
+                        <configuration>
+                            <repository>onap/${project.groupId}.${project.artifactId}</repository>
+                            <tag>${project.version}</tag>
+                            <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.2</version>
+            </plugin>
+        </plugins>
+        <finalName>mod-catalog-service</finalName>
+    </build>
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+                <version>2.7</version>
+            </plugin>
+        </plugins>
+    </reporting>
+</project>
\ No newline at end of file
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/ModCatalogApplication.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/ModCatalogApplication.java
new file mode 100644 (file)
index 0000000..d514a69
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.util.ArrayList;
+
+/**
+ * The application class
+ */
+@SpringBootApplication
+@EnableSwagger2
+public class ModCatalogApplication {
+
+       public static void main(String[] args) {
+               SpringApplication.run(ModCatalogApplication.class, args);
+       }
+       
+       @Bean
+       public Docket swaggerConfiguration(){
+               // return a prepared Docket instance
+               return new Docket(DocumentationType.SWAGGER_2)
+                               .select()
+                               //.paths(PathSelectors.ant("/api/*"))
+                               .apis(RequestHandlerSelectors.basePackage("org.onap.dcaegen2.platform.mod"))
+                               .build()
+                               .apiInfo(apiDetails());
+       }
+
+       private ApiInfo apiDetails() {
+               Contact DEFAULT_CONTACT = new Contact("", "", "");
+               return new ApiInfo(
+                               "MOD APIs",
+                               "APIs for MOD",
+                               "1.0.0"
+                               ,"", DEFAULT_CONTACT, "", "", new ArrayList<>()
+               );
+       }
+       //http://localhost:8080/swagger-ui.html for web page view
+       //http://localhost:8080/v2/api-docs for json view
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mock/MockDeploymentArtifactGenerator.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mock/MockDeploymentArtifactGenerator.java
new file mode 100644 (file)
index 0000000..24c31ed
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mock;
+
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactGeneratorStrategy;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * Mock implementation for DeploymentArtifactGenerator
+ */
+@Component
+public class MockDeploymentArtifactGenerator implements DeploymentArtifactGeneratorStrategy {
+
+    /**
+     * null implementation.
+     * @param activeSpec
+     * @param release
+     * @return
+     */
+    @Override
+    public Map<String, Object> generateForRelease(Specification activeSpec, String release) {
+        return null;
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mock/MockSpecificationValidationStratergy.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mock/MockSpecificationValidationStratergy.java
new file mode 100644 (file)
index 0000000..8f9f921
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mock;
+
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationValidationStratergy;
+import org.springframework.stereotype.Component;
+
+/**
+ * Mock implementation for SpecificationValidationStrategy
+ */
+@Component
+public class MockSpecificationValidationStratergy implements SpecificationValidationStratergy {
+
+    /**
+     * Mock implementation
+     * @param specificationRequest
+     * @param release
+     */
+    @Override
+    public void validate(SpecificationRequest specificationRequest, String release) {
+        //do nothing
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMicroservice.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMicroservice.java
new file mode 100644 (file)
index 0000000..3e5891f
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.basemicroservice;
+
+import org.onap.dcaegen2.platform.mod.model.common.AuditFields;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A model class which represents Base-Microservice entity
+ */
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+//TODO: migrate the document to microservices
+@Document("base-microservices")
+public class BaseMicroservice {
+    private String id;
+
+    @ApiModelProperty(required = true)
+    private String name;
+
+    @ApiModelProperty(required = true)
+    private String tag;
+
+    private String serviceName;
+
+    private BaseMsType type;
+
+    private BaseMsLocation location;
+
+    private String namespace;
+
+    private BaseMsStatus status;
+
+    private AuditFields metadata;
+
+    private List<Map<String, String>> msInstances = new ArrayList<>();
+}
+
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsLocation.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsLocation.java
new file mode 100644 (file)
index 0000000..e8114a5
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.basemicroservice;
+/**
+ * Supported values for Base-Microservice location
+ */
+public enum BaseMsLocation {
+    CENTRAL,
+    EDGE,
+    UNSPECIFIED
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsStatus.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsStatus.java
new file mode 100644 (file)
index 0000000..4460e04
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.basemicroservice;
+
+/**
+ * Supported statuses for Base-Microservice entity
+ */
+public enum BaseMsStatus {
+    ACTIVE, RETIRED
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsType.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/basemicroservice/BaseMsType.java
new file mode 100644 (file)
index 0000000..1cc81e8
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.basemicroservice;
+
+/**
+ * Supported Types for Base-Microservice
+ */
+public enum BaseMsType {
+    TICK,
+    FM_COLLECTOR,
+    PM_COLLECTOR,
+    ANALYTIC,
+    OTHER
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/common/AuditFields.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/common/AuditFields.java
new file mode 100644 (file)
index 0000000..d062456
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.common;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A model class for auditfields
+ */
+@Data
+@Builder
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AuditFields {
+
+    private String createdBy;
+    private Date createdOn;
+    private String updatedBy;
+    private Date updatedOn;
+    //TODO set default empty values if not exist
+    private String notes = "";
+    private List<String> labels = new ArrayList<>();
+
+    public void setLabels(Object labels) {
+        if(labels instanceof List)
+            this.labels = (List<String>) labels;
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifact.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifact.java
new file mode 100644 (file)
index 0000000..600c735
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.deploymentartifact;
+
+import lombok.Data;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Map;
+
+/**
+ * A model class which represents Deployment-Artifact entity
+ */
+@Data
+@Document("deployment-artifact")
+public class DeploymentArtifact {
+
+    private String id;
+
+    private Integer version;
+
+    private String content;
+
+    private String fileName;
+
+    private DeploymentArtifactStatus status;
+
+    private Map<String, Object> metadata;
+
+    private MsInstanceInfo msInstanceInfo;
+
+    private Map<String, Object> specificationInfo;
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactFilter.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactFilter.java
new file mode 100644 (file)
index 0000000..870ca46
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.deploymentartifact;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * A model class to construct a filter that can be passed in DeploymentArtifactSearch
+ * @see org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch
+ */
+@Data
+public class DeploymentArtifactFilter {
+    @JsonProperty("release")
+    private String release;
+
+    @JsonProperty("status")
+    private DeploymentArtifactStatus status;
+
+    @JsonProperty("tag")
+    private String tag;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactSearch.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactSearch.java
new file mode 100644 (file)
index 0000000..9e955cd
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.deploymentartifact;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * A model class to define Deployment-Artifact's searching criteria.
+ */
+@Data
+public class DeploymentArtifactSearch {
+
+    @JsonProperty("filter")
+    private DeploymentArtifactFilter filter;
+
+    public DeploymentArtifactSearch() {
+        this.filter = new DeploymentArtifactFilter();
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactStatus.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/DeploymentArtifactStatus.java
new file mode 100644 (file)
index 0000000..4ee4e0e
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.deploymentartifact;
+
+/**
+ * Supported Statuses for a Deployment-Artifact
+ */
+public enum DeploymentArtifactStatus {
+
+    IN_DEV, NOT_NEEDED, DEV_COMPLETE,
+    IN_PROD, PROD_FAILED
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/MsInstanceInfo.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/deploymentartifact/MsInstanceInfo.java
new file mode 100644 (file)
index 0000000..665961c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.deploymentartifact;
+
+import lombok.Data;
+
+/**
+ * MsInstance Reference model used in DeploymentArtifact entity
+ */
+@Data
+public class MsInstanceInfo {
+    String id;
+    String name;
+    String release;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ErrorMessages.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ErrorMessages.java
new file mode 100644 (file)
index 0000000..9b96066
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions;
+
+public class ErrorMessages {
+
+    public static final String MICROSERVICE_NAME_CONFLICT_MESSAGE = "Microservice with this name already exists.";
+    public static final String MICROSERVICE_TAG_CONFLICT_MESSAGE = "Microservice with this tag name already exists.";
+    public static final String MS_TAG_NAME_VALIDATION_MESSAGE =
+            "Microservice tag name is Invalid. Accepts lowercase letters and hyphens." +
+                    " Tag name length cannot exceed 50 characters";
+    public static final String MS_SERVICE_NAME_CONFLICT_MESSAGE = "Microservice with this core name already exists.";
+    public static final String MS_SERVICE_NAME_VALIDATION_MESSAGE =
+            "Service name is Invalid. Accepts lowercase letters and hyphens";
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/MissingRequestBodyException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/MissingRequestBodyException.java
new file mode 100644 (file)
index 0000000..2d7d785
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions;
+
+public class MissingRequestBodyException extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/OperationNotAllowedException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/OperationNotAllowedException.java
new file mode 100644 (file)
index 0000000..47dafc9
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions;
+
+public class OperationNotAllowedException extends RuntimeException {
+    public OperationNotAllowedException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ReleaseNotSupportedException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ReleaseNotSupportedException.java
new file mode 100644 (file)
index 0000000..7ea6834
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions;
+
+public class ReleaseNotSupportedException extends RuntimeException{
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ResourceConflictException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/ResourceConflictException.java
new file mode 100644 (file)
index 0000000..d81c68c
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions;
+
+public class ResourceConflictException extends RuntimeException{
+    public ResourceConflictException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceNotFoundException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceNotFoundException.java
new file mode 100644 (file)
index 0000000..3ee81e4
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice;
+
+public class BaseMicroserviceNotFoundException extends RuntimeException {
+    public BaseMicroserviceNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceTagAlreadyExists.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceTagAlreadyExists.java
new file mode 100644 (file)
index 0000000..e0bd119
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice;
+
+public class BaseMicroserviceTagAlreadyExists extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceTagInvalid.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMicroserviceTagInvalid.java
new file mode 100644 (file)
index 0000000..79282aa
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice;
+
+public class BaseMicroserviceTagInvalid extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMsServiceNameAlreadyExists.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMsServiceNameAlreadyExists.java
new file mode 100644 (file)
index 0000000..3ae1b6f
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice;
+
+public class BaseMsServiceNameAlreadyExists extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMsServiceNameInvalid.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/basemicroservice/BaseMsServiceNameInvalid.java
new file mode 100644 (file)
index 0000000..6de3014
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice;
+
+public class BaseMsServiceNameInvalid extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/common/UserNotPassedException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/common/UserNotPassedException.java
new file mode 100644 (file)
index 0000000..b34e065
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.common;
+
+public class UserNotPassedException extends RuntimeException {
+    public UserNotPassedException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/BlueprintFileNameCreateException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/BlueprintFileNameCreateException.java
new file mode 100644 (file)
index 0000000..5f52885
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact;
+
+public class BlueprintFileNameCreateException extends RuntimeException{
+    public BlueprintFileNameCreateException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/DeploymentArtifactNotFound.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/DeploymentArtifactNotFound.java
new file mode 100644 (file)
index 0000000..e10c54e
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact;
+
+public class DeploymentArtifactNotFound extends RuntimeException{
+    public DeploymentArtifactNotFound(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/StatusChangeNotValidException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/deploymentartifact/StatusChangeNotValidException.java
new file mode 100644 (file)
index 0000000..99faf50
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact;
+
+public class StatusChangeNotValidException extends RuntimeException {
+
+    public StatusChangeNotValidException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/msinstance/MsInstanceAlreadyExistsException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/msinstance/MsInstanceAlreadyExistsException.java
new file mode 100644 (file)
index 0000000..80778ea
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.msinstance;
+
+public class MsInstanceAlreadyExistsException extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/msinstance/MsInstanceNotFoundException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/msinstance/MsInstanceNotFoundException.java
new file mode 100644 (file)
index 0000000..9ea977c
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.msinstance;
+
+public class MsInstanceNotFoundException extends  RuntimeException{
+    public MsInstanceNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/specification/SpecificationInvalid.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/specification/SpecificationInvalid.java
new file mode 100644 (file)
index 0000000..1b56d35
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.specification;
+
+import java.util.Map;
+
+public class SpecificationInvalid extends RuntimeException {
+
+    Map<String, Object> errorsMap;
+
+    public SpecificationInvalid(String message) {
+        super(message);
+    }
+
+    public SpecificationInvalid(Map<String, Object> errorsMap){
+        this.errorsMap = errorsMap;
+    }
+
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/specification/SpecificationNotFoundException.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/exceptions/specification/SpecificationNotFoundException.java
new file mode 100644 (file)
index 0000000..9728b15
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.exceptions.specification;
+
+public class SpecificationNotFoundException extends RuntimeException {
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/DeploymentArtifactsRef.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/DeploymentArtifactsRef.java
new file mode 100644 (file)
index 0000000..fe1aed5
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.microserviceinstance;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * DeploymentArtifacts Reference model used in Microservice-Instance entity
+ */
+@Data
+public class DeploymentArtifactsRef {
+
+    private int mostRecentVersion;
+    private List<String> deploymentArtifacts;
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/MsInstance.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/MsInstance.java
new file mode 100644 (file)
index 0000000..d305aa4
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Map;
+
+/**
+ * A model class which represents Microservice-Instance entity
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Document("ms-instance")
+public class MsInstance {
+    @Id
+    private String id;
+    private String name;
+    private String release;
+    private String version;
+    private MsInstanceStatus status;
+    private Map<String, Object> metadata;
+    private Map<String,Object> msInfo;
+
+    @DBRef
+    private Specification activeSpec;
+
+    private DeploymentArtifactsRef deploymentArtifactsInfo;
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/MsInstanceStatus.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/microserviceinstance/MsInstanceStatus.java
new file mode 100644 (file)
index 0000000..223f3dc
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.microserviceinstance;
+
+/**
+ * Supported Statuses for Microservice-Instance entity
+ */
+public enum MsInstanceStatus {
+    NEW, IN_DEV, DEV_COMPLETE, IN_TEST, CERTIFIED, PROD_DEPLOYED
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/DeploymentArtifactPatchRequest.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/DeploymentArtifactPatchRequest.java
new file mode 100644 (file)
index 0000000..8d52488
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ *  A model that represent request body to patch DeploymentArtifact entity.
+ */
+@Data
+public class DeploymentArtifactPatchRequest {
+
+    @JsonProperty("status")
+    private DeploymentArtifactStatus status;
+
+    @JsonProperty("metadata")
+    private Map<String, Object> metaData;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/ErrorResponse.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/ErrorResponse.java
new file mode 100644 (file)
index 0000000..fe78f94
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ *  A model that represent response body to send  a simple error response.
+ */
+@Data
+@AllArgsConstructor
+public class ErrorResponse {
+
+    private String message;
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/GenericErrorResponse.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/GenericErrorResponse.java
new file mode 100644 (file)
index 0000000..76597e2
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import lombok.Data;
+import org.springframework.http.HttpStatus;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *  A model that represent response body to send a detailed error response.
+ */
+@Data
+public class GenericErrorResponse {
+    private List<String> errors = new ArrayList<>();
+    private HttpStatus status;
+    private String message;
+
+    public GenericErrorResponse() {
+    }
+
+    public GenericErrorResponse(List<String> errors, HttpStatus status, String message) {
+        this.errors = errors;
+        this.status = status;
+        this.message = message;
+    }
+
+    public GenericErrorResponse(String error, HttpStatus status, String message) {
+        this.status = status;
+        this.message = message;
+        errors = Arrays.asList(error);
+    }
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MicroserviceCreateRequest.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MicroserviceCreateRequest.java
new file mode 100644 (file)
index 0000000..f8288cb
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsLocation;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsType;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.util.Map;
+
+/**
+ *  A model that represent request body to create a Microservice entity.
+ */
+@NoArgsConstructor
+@Data
+public class MicroserviceCreateRequest {
+
+    @NotBlank(message = "Microservice tag can not be blank")
+    @Pattern(regexp = "^([a-z0-9](-[a-z0-9])*)+$", message = "Microservice tag name is Invalid. Accepts alphanumerics and hyphens.")
+    @Size(min = 5, max = 50, message = "Tag name length cannot exceed 50 characters")
+    private String tag;
+
+    @NotBlank(message = "Microservice name cannot be blank")
+    private String name;
+
+    @Pattern(regexp = "^[a-z-]*$", message = "Microservice core name is Invalid. Accepts lowercase letters and hyphens.")
+    private String serviceName;
+
+    private BaseMsType type;
+    private BaseMsLocation location;
+    private String namespace;
+    private Map<String, Object> metadata;
+
+    @NotBlank(message = "user can not be blank")
+    private String user;
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MicroserviceUpdateRequest.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MicroserviceUpdateRequest.java
new file mode 100644 (file)
index 0000000..a7262d6
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsLocation;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsType;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import java.util.Map;
+
+/**
+ *  A model that represent request body to update a Microservice entity.
+ */
+@NoArgsConstructor
+@Data
+public class MicroserviceUpdateRequest {
+
+    @Pattern(regexp = "^(?!\\s*$).+", message = "must not be blank")
+    private String name;
+
+    @Pattern(regexp = "^[a-z-]*$", message = "Microservice core name is Invalid. Accepts lowercase letters and hyphens.")
+    private String serviceName;
+
+    private BaseMsType type;
+    private BaseMsLocation location;
+    private String namespace;
+    private Map<String, Object> metadata;
+
+    @NotBlank(message = "user cannot be blank")
+    private String user;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MsInstanceRequest.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MsInstanceRequest.java
new file mode 100644 (file)
index 0000000..803d417
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import java.util.Map;
+
+/**
+ *  A model that represent request body to create MsInsance entity.
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MsInstanceRequest {
+
+    @NotBlank
+    private String name;
+
+    @NotBlank
+    private String release;
+
+    private String version;
+
+    @NotBlank
+    private String user;
+
+    private Map<String,Object> metadata;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MsInstanceUpdateRequest.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/MsInstanceUpdateRequest.java
new file mode 100644 (file)
index 0000000..2af61a8
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.util.Map;
+
+/**
+ *  A model that represent request body to patch MsInstance entity.
+ */
+@Data
+public class MsInstanceUpdateRequest {
+
+    private String release;
+    private String version;
+    private Map<String,Object> metadata;
+
+    @NotBlank(message = "User cannot be blank.")
+    private String user;
+}
+
+
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/SpecificationRequest.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/SpecificationRequest.java
new file mode 100644 (file)
index 0000000..e34379e
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import org.onap.dcaegen2.platform.mod.model.specification.DeploymentType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ *  A model that represent request body to create Specification entity.
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class SpecificationRequest {
+
+    private Map<String,Object> specContent;
+    private Map<String, Object> policyJson;
+    private DeploymentType type;
+    private String user;
+    private Map<String,Object> metadata;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/SuccessResponse.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/restapi/SuccessResponse.java
new file mode 100644 (file)
index 0000000..d428407
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.restapi;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ *  A model that represent a simple Success response body.
+ */
+@Data
+@AllArgsConstructor
+public class SuccessResponse {
+
+    private String message;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/DeploymentType.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/DeploymentType.java
new file mode 100644 (file)
index 0000000..815a529
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.specification;
+
+/**
+ * Supported Deployment Types in Specification entity
+ */
+public enum DeploymentType {
+    K8S, DOCKER
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/Specification.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/Specification.java
new file mode 100644 (file)
index 0000000..32778a3
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.model.specification;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Map;
+
+/**
+ * A model class which represents Specification entity
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Document("specification")
+public class Specification {
+
+    @Id
+    private String id;
+    private SpecificationStatus status;
+    private Map<String,Object> specContent;
+    private Map<String, Object> policyJson;
+    private DeploymentType type;
+    private Map<String,Object> metadata;
+    private Map<String,Object> msInstanceInfo;
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/SpecificationStatus.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/model/specification/SpecificationStatus.java
new file mode 100644 (file)
index 0000000..3e6cd1a
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.model.specification;
+
+/**
+ * Supported statuses for Specification entity
+ */
+public enum SpecificationStatus {
+    ACTIVE, INACTIVE
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/basemicroservice/BaseMicroserviceMongoGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/basemicroservice/BaseMicroserviceMongoGateway.java
new file mode 100644 (file)
index 0000000..9101234
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.basemicroservice;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.BaseMicroserviceGateway;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Mongo implementation of BaseMicroserviceGateway
+ */
+@Service
+public class BaseMicroserviceMongoGateway implements BaseMicroserviceGateway {
+
+    @Autowired
+    BaseMicroserviceMongoRepo repo;
+
+    @Override
+    public Optional<BaseMicroservice> findByName(String name) {
+        return repo.findByNameIgnoreCase(name);
+    }
+
+    @Override
+    public Optional<BaseMicroservice> findByTag(String tag) {
+        return repo.findByTagIgnoreCase(tag);
+    }
+
+    @Override
+    public Optional<BaseMicroservice> findByServiceName(String serviceName) {
+        return repo.findByServiceNameIgnoreCase(serviceName);
+    }
+
+    @Override
+    public BaseMicroservice save(BaseMicroservice microservice) {
+        return repo.save(microservice);
+    }
+
+    @Override
+    public List<BaseMicroservice> findAll() {
+        Sort sortByCreatedDate = Sort.by(Sort.Direction.DESC, "metadata.createdOn");
+        return repo.findAll(sortByCreatedDate);
+    }
+
+    @Override
+    public Optional<BaseMicroservice> findById(String msId) {
+        return Optional.empty();
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/basemicroservice/BaseMicroserviceMongoRepo.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/basemicroservice/BaseMicroserviceMongoRepo.java
new file mode 100644 (file)
index 0000000..b3795b2
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.basemicroservice;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * An interface to use Spring MongoRepository
+ */
+@Repository
+public interface BaseMicroserviceMongoRepo extends MongoRepository<BaseMicroservice, String> {
+
+    Optional<BaseMicroservice> findByNameIgnoreCase(String name);
+
+    Optional<BaseMicroservice> findByTagIgnoreCase(String tag);
+
+    Optional<BaseMicroservice> findByServiceNameIgnoreCase(String serviceName);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/deploymentartifact/DeploymentArtifactMongoGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/deploymentartifact/DeploymentArtifactMongoGateway.java
new file mode 100644 (file)
index 0000000..72e9ec6
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.MsInstanceInfo;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactGateway;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.ExampleMatcher;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Mongo implementation of BaseMicroserviceGateway
+ */
+@Service
+public class DeploymentArtifactMongoGateway implements DeploymentArtifactGateway {
+
+    @Autowired
+    DeploymentArtifactMongoRepo crudRepo;
+
+    public DeploymentArtifactMongoGateway(DeploymentArtifactMongoRepo repo) {
+        this.crudRepo = repo;
+    }
+
+    @Override
+    public List<DeploymentArtifact> findAll() {
+        return crudRepo.findAll();
+    }
+
+    @Override
+    public List<DeploymentArtifact> findByMsInstanceId(String id) {
+        return crudRepo.findByMsInstanceInfo_Id(id);
+    }
+
+    @Override
+    public Optional<DeploymentArtifact> findById(String id) {
+        return crudRepo.findById(id);
+    }
+
+    @Override
+    public void deleteById(String deploymentArtifactId) {
+        crudRepo.deleteById(deploymentArtifactId);
+    }
+
+    @Override
+    public DeploymentArtifact save(DeploymentArtifact deploymentArtifact) {
+        return crudRepo.save(deploymentArtifact);
+    }
+
+    @Override
+    public List<DeploymentArtifact> findAll(DeploymentArtifactSearch search) {
+        DeploymentArtifact artifact = new DeploymentArtifact();
+        artifact.setStatus(search.getFilter().getStatus());
+        //Currently searching tag in filename as it is not present in DeploymentArtifact record
+        artifact.setFileName(search.getFilter().getTag());
+
+        MsInstanceInfo msInstanceInfo = new MsInstanceInfo();
+        msInstanceInfo.setRelease(search.getFilter().getRelease());
+        artifact.setMsInstanceInfo(msInstanceInfo);
+
+        return crudRepo.findAll(Example.of(artifact,ExampleMatcher.matching().withIgnoreCase()));
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/deploymentartifact/DeploymentArtifactMongoRepo.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/deploymentartifact/DeploymentArtifactMongoRepo.java
new file mode 100644 (file)
index 0000000..cb8d0bb
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * An interface to use Spring MongoRepository
+ */
+@Repository
+public interface DeploymentArtifactMongoRepo extends MongoRepository<DeploymentArtifact, String> {
+
+    List<DeploymentArtifact> findByMsInstanceInfo_Id(String id);
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/microserviceinstance/MsInstanceMongoGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/microserviceinstance/MsInstanceMongoGateway.java
new file mode 100644 (file)
index 0000000..d6d0d35
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceGateway;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Mongo implementation of MsInstance
+ */
+@Service
+public class MsInstanceMongoGateway implements MsInstanceGateway {
+
+    @Autowired
+    private MsInstanceMongoRepo repo;
+
+    @Override
+    public Optional<MsInstance> findByNameAndRelease(String name, String release) {
+        return repo.findByNameIgnoreCaseAndReleaseIgnoreCase(name, release);
+    }
+
+    @Override
+    public Optional<MsInstance> findById(String msInstanceId) {
+        return repo.findById(msInstanceId);
+    }
+
+    @Override
+    public List<MsInstance> findAll() {
+        Sort sortByCreatedDate = Sort.by(Sort.Direction.DESC, "metadata.createdOn");
+        return repo.findAll(sortByCreatedDate);
+    }
+
+    @Override
+    public MsInstance save(MsInstance msInstance) {
+        return repo.save(msInstance);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/microserviceinstance/MsInstanceMongoRepo.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/microserviceinstance/MsInstanceMongoRepo.java
new file mode 100644 (file)
index 0000000..1d6a277
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * An interface to use Spring MongoRepository
+ */
+@Repository
+public interface MsInstanceMongoRepo extends MongoRepository<MsInstance, String> {
+
+    Optional<MsInstance> findByNameIgnoreCaseAndReleaseIgnoreCase(String name, String release);
+
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/specification/SpecificationMongoGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/specification/SpecificationMongoGateway.java
new file mode 100644 (file)
index 0000000..ae3a421
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.specification;
+
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationGateway;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * Mongo implementation of Specification
+ */
+@Service
+public class SpecificationMongoGateway implements SpecificationGateway {
+
+    @Autowired
+    private SpecificationMongoRepo repo;
+
+    @Override
+    public List<Specification> getSpecificationByMsInstanceId(String id) {
+        return repo.getSpecificationsByMsInstaceId(id);
+    }
+
+    @Override
+    public Specification save(Specification newSpec) {
+        return repo.save(newSpec);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/specification/SpecificationMongoRepo.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/mongo/specification/SpecificationMongoRepo.java
new file mode 100644 (file)
index 0000000..306e78a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.mongo.specification;
+
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.mongodb.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * An interface to use Spring MongoRepository
+ */
+@Repository
+public interface SpecificationMongoRepo extends MongoRepository<Specification, String> {
+
+    @Query(value = "{'msInstanceInfo.id':?0}")
+    List<Specification> getSpecificationsByMsInstaceId(String id);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/AppExceptionHandler.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/AppExceptionHandler.java
new file mode 100644 (file)
index 0000000..1a2f5f9
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.controller;
+
+import org.onap.dcaegen2.platform.mod.model.exceptions.MissingRequestBodyException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.OperationNotAllowedException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.ResourceConflictException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice.BaseMicroserviceNotFoundException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.common.UserNotPassedException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.DeploymentArtifactNotFound;
+import org.onap.dcaegen2.platform.mod.model.exceptions.msinstance.MsInstanceNotFoundException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.specification.SpecificationInvalid;
+import org.onap.dcaegen2.platform.mod.model.restapi.ErrorResponse;
+import org.onap.dcaegen2.platform.mod.model.restapi.GenericErrorResponse;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * a class to manage all exceptions
+ */
+@ControllerAdvice
+@Slf4j
+public class AppExceptionHandler {
+
+    @ExceptionHandler(value = {WebClientResponseException.class})
+    public ResponseEntity<ErrorResponse> handleCompSpecInvalidException
+            (WebClientResponseException ex, WebRequest request) {
+        return new ResponseEntity<ErrorResponse>
+                (new ErrorResponse(ex.getResponseBodyAsString()), HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @ExceptionHandler(value = {JsonParseException.class})
+    public ResponseEntity<GenericErrorResponse> handleJsonParsedException
+            (JsonParseException ex, WebRequest request) {
+
+        GenericErrorResponse response = new GenericErrorResponse();
+        response.setStatus(HttpStatus.BAD_REQUEST);
+        response.setMessage("Invalid JSON request body format.");
+        response.setErrors(Arrays.asList(ex.getMessage()));
+        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<GenericErrorResponse> specificationInvalid(SpecificationInvalid ex) {
+        Map<String, Object> errorResponse = new Gson().fromJson(ex.getMessage(), Map.class);
+        GenericErrorResponse response = new GenericErrorResponse();
+        response.setMessage((String) errorResponse.get("summary"));
+        response.setStatus(HttpStatus.BAD_REQUEST);
+        response.setErrors((List<String>) errorResponse.get("errors"));
+        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<GenericErrorResponse> missingRequestBodyException(MissingRequestBodyException ex){
+        GenericErrorResponse response = new GenericErrorResponse();
+        response.setMessage("Missing paramaters");
+        response.setStatus(HttpStatus.BAD_REQUEST);
+        response.setErrors(Arrays.asList("Missing required request body paramaters"));
+        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveUserNotPassedException(UserNotPassedException ex){
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()), HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveMsInstanceNotFoundException(MsInstanceNotFoundException ex) {
+        log.error(ex.getMessage());
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()), HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveDeploymentArtifactNotFound(DeploymentArtifactNotFound ex) {
+        log.error(ex.getMessage());
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()),
+                HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveOperationNotAllowed(OperationNotAllowedException ex) {
+        log.error(ex.getMessage());
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()),
+                HttpStatus.CONFLICT);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveResourceConflict(ResourceConflictException ex) {
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()), HttpStatus.CONFLICT);
+    }
+
+    @ExceptionHandler(value = {BaseMicroserviceNotFoundException.class})
+    public ResponseEntity<ErrorResponse> resolveResourceNotFoundExcetions(RuntimeException ex) {
+        log.error(ex.getMessage(), ex);
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()), HttpStatus.CONFLICT);
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<GenericErrorResponse> resolveBeanValidationException(MethodArgumentNotValidException ex){
+        log.error(ex.getMessage());
+        GenericErrorResponse response = new GenericErrorResponse();
+        response.setMessage("Validation failed.");
+        response.setStatus(HttpStatus.BAD_REQUEST);
+        response.setErrors(getBeanValidationErrors(ex));
+        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+    }
+
+    private List<String> getBeanValidationErrors(MethodArgumentNotValidException ex) {
+        List<String> errors = new ArrayList<>();
+        ex.getBindingResult().getAllErrors().forEach((error) -> {
+            String fieldName = ((FieldError) error).getField();
+            String errorMessage = error.getDefaultMessage();
+            errors.add(String.format("%s: %s", fieldName, errorMessage));
+        });
+        return errors;
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/BaseMicroserviceController.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/BaseMicroserviceController.java
new file mode 100644 (file)
index 0000000..073b93a
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.controller;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.MsService;
+import io.swagger.annotations.Api;
+import lombok.Setter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+import static org.onap.dcaegen2.platform.mod.web.controller.BaseMicroserviceController.API_BASE_MICROSERVICE;
+
+/**
+ * Controller class to manage Microservice's REST endpoints
+ */
+@CrossOrigin
+@RestController
+@RequestMapping(API_BASE_MICROSERVICE)
+@Api(tags = "Base Microservice", description = "APIs to manage Base Microservice")
+public class BaseMicroserviceController {
+
+    public static final String API_BASE_MICROSERVICE = "/api/base-microservice";
+    @Autowired
+    @Setter
+    MsService baseMsService;
+
+    @GetMapping
+    public List<BaseMicroservice> getAll() {
+        return baseMsService.getAllMicroservices();
+    }
+
+
+    @PostMapping
+    @ResponseStatus(HttpStatus.CREATED)
+    public BaseMicroservice createMicroservice(@RequestBody @Valid MicroserviceCreateRequest request) {
+        return baseMsService.createMicroservice(request);
+    }
+
+    @PatchMapping(value = "/{msId}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void updateMicroservice(@RequestBody @Valid MicroserviceUpdateRequest request,
+                                   @PathVariable("msId") String msId){
+        baseMsService.updateMicroservice(msId, request);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/DeploymentArtifactController.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/DeploymentArtifactController.java
new file mode 100644 (file)
index 0000000..e1cd512
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.controller;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.BlueprintFileNameCreateException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.StatusChangeNotValidException;
+import org.onap.dcaegen2.platform.mod.model.restapi.DeploymentArtifactPatchRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.ErrorResponse;
+import org.onap.dcaegen2.platform.mod.model.restapi.SuccessResponse;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactService;
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.onap.dcaegen2.platform.mod.web.controller.DeploymentArtifactController.DEPLOYMENT_ARTIFACTS_BASE_URL;
+
+/**
+ * Controller class to manage DeploymentArtifact's REST endpoints
+ */
+@RestController
+@CrossOrigin
+@RequestMapping(DEPLOYMENT_ARTIFACTS_BASE_URL)
+@Slf4j
+@Api(tags = "Deployment Artifact", value = "APIs to manage Deployment Artifacts")
+public class DeploymentArtifactController {
+
+    public static final String DEPLOYMENT_ARTIFACTS_BASE_URL = "/api/deployment-artifact";
+
+    public static final String GET_STATUSES = "/statuses";
+
+    @Autowired
+    private DeploymentArtifactService service;
+
+    @PostMapping("/{msInstanceId}")
+    @ResponseStatus(HttpStatus.CREATED)
+    public DeploymentArtifact generateDeploymentArtifactForMSInstance(@PathVariable String  msInstanceId,
+                                                                      @RequestParam String user ){
+        return service.generateDeploymentArtifact(msInstanceId, user);
+    }
+
+    @GetMapping
+    @ResponseStatus(HttpStatus.OK)
+    public List<DeploymentArtifact> getAllDeploymentArtifacts(){
+        return service.getAllDeploymentArtifacts();
+    }
+
+    @PostMapping("/search")
+    @ResponseStatus(HttpStatus.OK)
+    public List<DeploymentArtifact> searchDeploymentArtifacts(@RequestBody DeploymentArtifactSearch searchRequest){
+        log.info("Search on deployment artifacts: {}", searchRequest);
+        return service.searchDeploymentArtifacts(searchRequest);
+    }
+
+    @GetMapping(GET_STATUSES)
+    @ResponseStatus(HttpStatus.OK)
+    public List<DeploymentArtifactStatus> getDeploymentArtifactStatuses(){
+        return Arrays.asList(DeploymentArtifactStatus.values());
+    }
+
+    @PatchMapping(value = "/{deploymentArtifactId}", params = {"user!="})
+    public ResponseEntity<SuccessResponse> patchDeploymentArtifact(@PathVariable("deploymentArtifactId") String id,
+                                                     @RequestBody DeploymentArtifactPatchRequest deploymentArtifactPatchRequest,
+                                                     @RequestParam("user") String user){
+        log.info("***Received request {} to update DeploymentArtifact id {} by {}", deploymentArtifactPatchRequest, id, user);
+        service.updateDeploymentArtifact(id, deploymentArtifactPatchRequest, user);
+        return new ResponseEntity<>(new SuccessResponse("Deployment Artifact was updated."),HttpStatus.OK);
+    }
+
+    @DeleteMapping( value = "/{deploymentArtifactId}", params = {"user!="})
+    public ResponseEntity<SuccessResponse> deleteDeploymentArtifact(@PathVariable("deploymentArtifactId") String id,
+                                                      @RequestParam("user") String user){
+        log.info("***Received request to delete DeploymentArtifact id {} by {}", id, user);
+        service.deleteDeploymentArtifact(id);
+        return new ResponseEntity<>(new SuccessResponse("Deployment Artifact was deleted"), HttpStatus.OK);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveBaseMsServiceNameRegex(BlueprintFileNameCreateException ex) {
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()),
+                HttpStatus.BAD_REQUEST);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveStatusValidationFailure(StatusChangeNotValidException ex) {
+        return new ResponseEntity<>(new ErrorResponse(ex.getMessage()),
+                HttpStatus.BAD_REQUEST);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/MicroserviceInstanceController.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/MicroserviceInstanceController.java
new file mode 100644 (file)
index 0000000..14eb154
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.controller;
+
+import org.onap.dcaegen2.platform.mod.model.exceptions.msinstance.MsInstanceAlreadyExistsException;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.ErrorResponse;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * Controller class to manage MicroserviceInstance's REST endpoints
+ */
+@CrossOrigin
+@RestController
+@Api(tags = "Microservice Instance", description = "APIs to manage Microservice Instance")
+@RequestMapping("/api/microservice-instance")
+public class MicroserviceInstanceController {
+
+    @Autowired
+    MsInstanceService msInstanceService;
+
+    @GetMapping
+    @ApiOperation("Get all Microservices Instances")
+    public List<MsInstance> getAll() {
+        return msInstanceService.getAll();
+    }
+
+
+    @PostMapping("/{msName}")
+    @ApiOperation("Create a Microservice Instance")
+    @ResponseStatus(HttpStatus.CREATED)
+    public MsInstance createMsInstance(@PathVariable String msName, @RequestBody MsInstanceRequest request) {
+        return msInstanceService.createMicroserviceInstance(msName, request);
+    }
+
+    @PatchMapping("/{msId}")
+    @ApiOperation("Patch a Microservice Instance")
+    @ResponseStatus(HttpStatus.OK)
+    public MsInstance patchMsInstance(@RequestBody MsInstanceUpdateRequest request, @PathVariable String msId){
+        return msInstanceService.updateMsInstance(request, msId);
+    }
+
+    @ExceptionHandler
+    public ResponseEntity<ErrorResponse> resolveMsInstanceConflict(MsInstanceAlreadyExistsException ex) {
+        return new ResponseEntity<>(new ErrorResponse("Microservice Instance for the given name and release already exists"), HttpStatus.CONFLICT);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/SpecificationController.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/controller/SpecificationController.java
new file mode 100644 (file)
index 0000000..854e7e6
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.controller;
+
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * Controller class to manage Specification's REST endpoints
+ */
+@CrossOrigin
+@RestController
+@Api(tags = "Component Specification", description = "APIs to manage Component Specifications")
+@RequestMapping("/api/specification")
+@Slf4j
+public class SpecificationController {
+
+    @Autowired
+    SpecificationService specificationService;
+
+    @GetMapping("/{msInstanceId}")
+    @ApiOperation("Get all specifications for a Microservice Instance")
+    public List<Specification> getAllSpecsByMsInstanceId(@PathVariable String msInstanceId) {
+        log.info(msInstanceId);
+        return specificationService.getAllSpecsByMsInstanceId(msInstanceId);
+    }
+
+    @PostMapping("/{msInstanceId}")
+    @ApiOperation("Create Specification for a Microservice Instance")
+    @ResponseStatus(HttpStatus.CREATED)
+    public Specification createSpecification(@PathVariable String msInstanceId, @RequestBody SpecificationRequest request) {
+        log.info(request.toString());
+        return specificationService.createSpecification(msInstanceId, request);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/BaseMicroserviceGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/BaseMicroserviceGateway.java
new file mode 100644 (file)
index 0000000..a516d90
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.basemicroservice;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * An interface to interact with BaseMicroservice persistence
+ */
+public interface BaseMicroserviceGateway {
+
+    List<BaseMicroservice> findAll();
+
+    Optional<BaseMicroservice> findByName(String name);
+
+    Optional<BaseMicroservice> findByTag(String tag);
+
+    Optional<BaseMicroservice> findByServiceName(String serviceName);
+
+    BaseMicroservice save(BaseMicroservice microservice);
+
+    Optional<BaseMicroservice> findById(String msId);
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/MsService.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/MsService.java
new file mode 100644 (file)
index 0000000..cdc1921
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.basemicroservice;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceUpdateRequest;
+
+import java.util.List;
+
+/**
+ * An interface to access Ms Services
+ */
+public interface MsService {
+    BaseMicroservice createMicroservice(MicroserviceCreateRequest microserviceRequest);
+
+    List<BaseMicroservice> getAllMicroservices();
+
+    BaseMicroservice getMicroserviceById(String baseMsId);
+
+    BaseMicroservice getMicroserviceByName(String msName);
+
+    void updateMicroservice(String requestedMsId, MicroserviceUpdateRequest updateRequest);
+
+    void saveMsInstanceReferenceToMs(BaseMicroservice microservice, MsInstance msInstance);
+
+    void updateMsInstanceRef(MsInstance msInstance);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/MsServiceImpl.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/basemicroservice/MsServiceImpl.java
new file mode 100644 (file)
index 0000000..9cf46e6
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.basemicroservice;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsStatus;
+import org.onap.dcaegen2.platform.mod.model.common.AuditFields;
+import org.onap.dcaegen2.platform.mod.model.exceptions.ErrorMessages;
+import org.onap.dcaegen2.platform.mod.model.exceptions.ResourceConflictException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice.BaseMicroserviceNotFoundException;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import lombok.Setter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MsService implementation
+ */
+@Service
+public class MsServiceImpl implements MsService {
+
+    @Autowired
+    @Setter
+    private BaseMicroserviceGateway repository;
+
+    @Autowired
+    @Setter
+    private MsInstanceService msInstanceService;
+
+    /**
+     * creates Microservice record
+     * @param microserviceRequest
+     * @return
+     */
+    @Override
+    public BaseMicroservice createMicroservice(MicroserviceCreateRequest microserviceRequest) {
+        checkIfThereAreAnyConflicts(microserviceRequest); //TODO: Make fields unique in entity itself
+        BaseMicroservice microservice = new BaseMsCreator().create(microserviceRequest);
+        return repository.save(microservice);
+    }
+
+    /**
+     * name, tag and serviceName are unique for the given ms. This method make sure that.
+     * */
+    private void checkIfThereAreAnyConflicts(MicroserviceCreateRequest microserviceRequest) {
+        checkIfMsNameAlreadyExists(microserviceRequest.getName());
+        checkIfMsTagAlreadyExists(microserviceRequest.getTag());
+        checkiIfServiceNameAlreadyExists(microserviceRequest.getServiceName());
+    }
+
+    private void checkIfMsNameAlreadyExists(String msName) {
+        if (repository.findByName(msName).isPresent())
+            throw new ResourceConflictException(ErrorMessages.MICROSERVICE_NAME_CONFLICT_MESSAGE);
+    }
+
+    private void checkIfMsTagAlreadyExists(String msTag) {
+        if (repository.findByTag(msTag).isPresent())
+            throw new ResourceConflictException(ErrorMessages.MICROSERVICE_TAG_CONFLICT_MESSAGE);
+    }
+
+    private void checkiIfServiceNameAlreadyExists(String serviceName) {
+        boolean serviceNameIsEmpty = serviceName == null || serviceName.isEmpty();
+        if(serviceNameIsEmpty)
+            return;
+        if (repository.findByServiceName(serviceName).isPresent())
+            throw new ResourceConflictException(ErrorMessages.MS_SERVICE_NAME_CONFLICT_MESSAGE);
+    }
+
+    /**
+     * lists all microservice records
+     * @return
+     */
+    @Override
+    public List<BaseMicroservice> getAllMicroservices() {
+        return repository.findAll();
+    }
+
+    /**
+     * gets a Mioroservice by id
+     * @param baseMsId
+     * @return
+     */
+    @Override
+    public BaseMicroservice getMicroserviceById(String baseMsId) {
+        return repository.findById(baseMsId).orElseThrow(() ->
+                new BaseMicroserviceNotFoundException(String.format("Microservice with id %s not found", baseMsId)));
+    }
+
+    /**
+     * gets a Microservice by name
+     * @param msName
+     * @return
+     */
+    @Override
+    public BaseMicroservice getMicroserviceByName(String msName) {
+        return repository.findByName(msName).orElseThrow(() ->
+                new BaseMicroserviceNotFoundException(String.format("Microservice with name %s not found", msName)));
+    }
+
+    /**
+     * updates a Microservice
+     * @param requestedMsId
+     * @param updateRequest
+     */
+    @Override
+    public void updateMicroservice(String requestedMsId, MicroserviceUpdateRequest updateRequest) {
+        BaseMicroservice microservice = getMicroserviceById(requestedMsId);
+        updateMetadata(updateRequest, microservice);
+        updateOtherFields(updateRequest, microservice);
+        repository.save(microservice);
+        msInstanceService.updateMicroserviceReference(microservice);
+    }
+
+    //TODO: Get rid of nulls!
+    private void updateMetadata(MicroserviceUpdateRequest updateRequest, BaseMicroservice microservice) {
+        if(updateRequest.getUser() != null){
+            microservice.getMetadata().setUpdatedBy(updateRequest.getUser());
+        }
+        if(updateRequest.getMetadata() != null && updateRequest.getMetadata().containsKey("notes")){
+            microservice.getMetadata().setNotes((String) updateRequest.getMetadata().get("notes"));
+        }
+        if(updateRequest.getMetadata() != null && updateRequest.getMetadata().containsKey("labels")){
+            microservice.getMetadata().setLabels((List<String>) updateRequest.getMetadata().get("labels"));
+        }
+        microservice.getMetadata().setUpdatedOn(new Date());
+    }
+
+    private void updateOtherFields(MicroserviceUpdateRequest updateRequest, BaseMicroservice microservice) {
+        if(updateRequest.getName() != null){
+            updateName(updateRequest, microservice);
+        }
+        if(updateRequest.getType() != null){
+            microservice.setType(updateRequest.getType());
+        }
+        if(updateRequest.getLocation() != null){
+            microservice.setLocation(updateRequest.getLocation());
+        }
+        if(updateRequest.getServiceName() != null){
+            updateServiceName(updateRequest, microservice);
+        }
+        if(updateRequest.getNamespace() != null){
+            microservice.setNamespace(updateRequest.getNamespace());
+        }
+    }
+
+    /**
+     * If name requested in the updateRequest doesn't match the name of the ms record which is being worked on,
+     * then only check for the uniqueness.
+     */
+    private void updateName(MicroserviceUpdateRequest updateRequest, BaseMicroservice microservice) {
+        boolean notMatchesWithCurrentName = !updateRequest.getName().equals(microservice.getName());
+        if(notMatchesWithCurrentName)
+            checkIfMsNameAlreadyExists(updateRequest.getName());
+        microservice.setName(updateRequest.getName());
+    }
+
+    /**
+     * If serviceName requested in the updateRequest doesn't match the serviceName of the ms record which is
+     * being worked on, then only check for the uniqueness.
+     */
+    private void updateServiceName(MicroserviceUpdateRequest updateRequest, BaseMicroservice microservice) {
+        boolean notMatchesWithCurrentServiceName = !updateRequest.getServiceName().equals(microservice.getServiceName());
+        if(notMatchesWithCurrentServiceName)
+            checkiIfServiceNameAlreadyExists(updateRequest.getServiceName());
+        microservice.setServiceName(updateRequest.getServiceName());
+    }
+
+    /**
+     * saves msInstance reference in a given Microservice
+     * @param microservice
+     * @param msInstance
+     */
+    @Override
+    public void saveMsInstanceReferenceToMs(BaseMicroservice microservice, MsInstance msInstance) {
+        microservice.getMsInstances().add(getMsInstanceReference(msInstance));
+        repository.save(microservice);
+    }
+
+    /**
+     * updates MsIntstance ref in Microservice record
+     * @param msInstance
+     */
+    @Override
+    public void updateMsInstanceRef(MsInstance msInstance) {
+        BaseMicroservice microservice = getMicroserviceById((String) msInstance.getMsInfo().get("id"));
+        List<Map<String, String>> msInstancesRef = microservice.getMsInstances();
+        msInstancesRef.forEach((ref) -> {
+            if(ref.get("id").equals(msInstance.getId()))
+                ref.put("name", msInstance.getName());
+        });
+        repository.save(microservice);
+    }
+
+    private Map<String, String> getMsInstanceReference(MsInstance msInstance) {
+        Map<String,String> msInstanceInfo = new HashMap<>();
+        msInstanceInfo.put("id", msInstance.getId());
+        msInstanceInfo.put("name", msInstance.getName());
+        return msInstanceInfo;
+    }
+
+    private class BaseMsCreator {
+
+        BaseMicroservice create(MicroserviceCreateRequest createRequest) {
+            BaseMicroservice microservice = new BaseMicroservice();
+            microservice.setLocation(createRequest.getLocation());
+            microservice.setName(createRequest.getName());
+            microservice.setTag(createRequest.getTag());
+            microservice.setServiceName(createRequest.getServiceName());
+            microservice.setNamespace(createRequest.getNamespace());
+            microservice.setStatus(BaseMsStatus.ACTIVE);
+            microservice.setType(createRequest.getType());
+            microservice.setMetadata(getMetadataFields(createRequest));
+            return microservice;
+        }
+
+        private AuditFields getMetadataFields(MicroserviceCreateRequest request) {
+            AuditFields auditFields = AuditFields.builder().build();
+            auditFields.setCreatedBy(request.getUser());
+            auditFields.setCreatedOn(new Date());
+
+            if (request.getMetadata().containsKey("notes"))
+                auditFields.setNotes((String) request.getMetadata().get("notes"));
+            if (request.getMetadata().containsKey("labels"))
+//                auditFields.setLabels((List<String>) request.getMetadata().get("labels"));
+                auditFields.setLabels(request.getMetadata().get("labels"));
+
+            return auditFields;
+
+        }
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/ArtifactFileNameCreator.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/ArtifactFileNameCreator.java
new file mode 100644 (file)
index 0000000..7713020
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.BlueprintFileNameCreateException;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.springframework.stereotype.Component;
+
+/**
+ * A name creator for Deployment Artifact files.
+ */
+@Component
+public class ArtifactFileNameCreator {
+
+    private static final String FILE_FORMAT = ".yaml";
+
+    /**
+     * creates a file name
+     * @param msInstance
+     * @param version
+     * @return
+     */
+    public String createFileName(MsInstance msInstance, int version) {
+        if(msInstance.getMsInfo() == null || !msInstance.getMsInfo().containsKey("tag")){
+            throwException("MS-tag");
+        }
+        if(msInstance.getActiveSpec() == null){
+            throwException("active-spec");
+        }
+        return  msInstance.getMsInfo().get("tag") + "_"
+                + msInstance.getActiveSpec().getType().toString().toLowerCase() + "_"
+                + msInstance.getRelease() + "_"
+                + version
+                + FILE_FORMAT;
+   }
+
+    private void throwException(String missingProperty) {
+        throw new BlueprintFileNameCreateException("Can not create bluerprint file name: "
+                + missingProperty + " is missing");
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactGateway.java
new file mode 100644 (file)
index 0000000..6bf2c2a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch;
+
+import java.util.List;
+import java.util.Optional;
+    
+    /**
+     * An interface to interact with DeploymentArtifact persistence
+     */
+public interface DeploymentArtifactGateway {
+
+    List<DeploymentArtifact> findAll();
+
+    List<DeploymentArtifact> findByMsInstanceId(String id);
+
+    Optional<DeploymentArtifact> findById(String id);
+
+    void deleteById(String deploymentArtifactId);
+
+    DeploymentArtifact save(DeploymentArtifact deploymentArtifact);
+
+    List<DeploymentArtifact> findAll(DeploymentArtifactSearch search);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactGeneratorStrategy.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactGeneratorStrategy.java
new file mode 100644 (file)
index 0000000..9bb0870
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+
+import java.util.Map;
+
+/**
+ * provides abstraction to generate Deployment Artifacts
+ */
+public interface DeploymentArtifactGeneratorStrategy {
+    Map<String, Object> generateForRelease(Specification activeSpec, String release);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactService.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactService.java
new file mode 100644 (file)
index 0000000..3e7f899
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.DeploymentArtifactPatchRequest;
+
+import java.util.List;
+
+/**
+ * An interface to access DeploymentArtifact Services
+ */
+public interface DeploymentArtifactService {
+
+    DeploymentArtifact generateDeploymentArtifact(String msInstanceId, String user);
+
+    List<DeploymentArtifact> getAllDeploymentArtifacts();
+
+    DeploymentArtifact findDeploymentArtifactById(String id);
+
+    void updateDeploymentArtifact(String deploymentArtifactId, DeploymentArtifactPatchRequest deploymentArtifactPatchRequest, String user);
+
+    List<DeploymentArtifact> findByMsInstanceId(String msInstanceId);
+
+    void deleteDeploymentArtifact(String deploymentArtifactId);
+
+    void updateMsInstanceRef(MsInstance msInstance);
+
+    List<DeploymentArtifact> searchDeploymentArtifacts(DeploymentArtifactSearch search);
+}
+
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactServiceImpl.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactServiceImpl.java
new file mode 100644 (file)
index 0000000..8b97bba
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.MsInstanceInfo;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.DeploymentArtifactNotFound;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.DeploymentArtifactsRef;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.DeploymentArtifactPatchRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * DeploymentArtifact Service implementation
+ */
+@Service
+@Slf4j
+@Setter
+public class DeploymentArtifactServiceImpl implements DeploymentArtifactService{
+
+    private static final String VERSION_KEY = "mostRecentVersion";
+
+    @Autowired
+    private MsInstanceService msInstanceService;
+
+    @Autowired
+    private DeploymentArtifactGeneratorStrategy deploymentArtifactGeneratorStrategy;
+
+    @Autowired
+    private DeploymentArtifactGateway deploymentArtifactGateway;
+
+    @Autowired
+    private ArtifactFileNameCreator fileNameCreator;
+
+    @Autowired
+    private DeploymentArtifactStatusChangeHandler statusChangeHandler;
+
+    ///////////////FIND METHODS//////////////////////////
+    @Override
+    public List<DeploymentArtifact> getAllDeploymentArtifacts() {
+        return deploymentArtifactGateway.findAll();
+    }
+
+    @Override
+    public List<DeploymentArtifact> searchDeploymentArtifacts(DeploymentArtifactSearch search) {
+        return deploymentArtifactGateway.findAll(search);
+    }
+
+    @Override
+    public DeploymentArtifact findDeploymentArtifactById(String id){
+        return deploymentArtifactGateway.findById(id).orElseThrow(
+                () -> new DeploymentArtifactNotFound("Deployment Artifact with id " + id + " not found")
+        );
+    }
+
+    @Override
+    public List<DeploymentArtifact> findByMsInstanceId(String msInstanceId) {
+        return deploymentArtifactGateway.findByMsInstanceId(msInstanceId);
+    }
+
+    @Override
+    @Transactional
+    public void deleteDeploymentArtifact(String deploymentArtifactId) {
+        DeploymentArtifact deploymentArtifact = findDeploymentArtifactById(deploymentArtifactId);
+        log.info("deleting {}", deploymentArtifact.getFileName());
+        deploymentArtifactGateway.deleteById(deploymentArtifactId);
+        msInstanceService.removeDeploymentArtifactFromMsInstance(deploymentArtifact);
+    }
+
+    @Override
+    @Transactional
+    public void updateMsInstanceRef(MsInstance msInstance) {
+        List<DeploymentArtifact> deploymentArtifacts = findByMsInstanceId(msInstance.getId());
+        deploymentArtifacts.forEach((deploymentArtifact) -> {
+            deploymentArtifact.getMsInstanceInfo().setName(msInstance.getName());
+            deploymentArtifact.getMsInstanceInfo().setRelease(msInstance.getRelease());
+            deploymentArtifactGateway.save(deploymentArtifact);
+        });
+    }
+
+    //////////////////////////////////////////////////////
+
+    @Override
+    @Transactional
+    //only status update was implemented
+    public void updateDeploymentArtifact(String deploymentArtifactId, DeploymentArtifactPatchRequest deploymentArtifactPatchRequest,
+                                         String user) {
+        DeploymentArtifact deploymentArtifact = findDeploymentArtifactById(deploymentArtifactId);
+        updateStatus(deploymentArtifactPatchRequest, deploymentArtifact);
+        updateMetadata(user, deploymentArtifact);
+        log.info("Updating the artifact in database..");
+        deploymentArtifactGateway.save(deploymentArtifact);
+        msInstanceService.updateStatusBasedOnDeploymentArtifactsStatuses(deploymentArtifact.getMsInstanceInfo().getId());
+    }
+
+    private void updateMetadata(String user, DeploymentArtifact deploymentArtifact) {
+        deploymentArtifact.getMetadata().put("updatedBy", user);
+        deploymentArtifact.getMetadata().put("updatedOn", new Date());
+    }
+
+    private void updateStatus(DeploymentArtifactPatchRequest deploymentArtifactPatchRequest, DeploymentArtifact deploymentArtifact) {
+        DeploymentArtifactStatus changeToStatus = deploymentArtifactPatchRequest.getStatus();
+        if(changeToStatus != null){
+            log.info("Sent request to deployment artifact status change handler: {}", changeToStatus);
+            statusChangeHandler.handleStatusChange(changeToStatus, deploymentArtifact);
+        }
+    }
+
+    @Override
+    @Transactional
+    public DeploymentArtifact generateDeploymentArtifact(String msInstanceId, String user) {
+        MsInstance msInstance = msInstanceService.getMsInstanceById(msInstanceId);
+
+        //Generate the Blueprint for the active specification for the instance
+       Map<String, Object> deploymentArtifact =  deploymentArtifactGeneratorStrategy.generateForRelease(msInstance.getActiveSpec(), msInstance.getRelease());
+
+        DeploymentArtifact artifact = new DeploymentArtifact();
+        artifact.setContent(String.valueOf(deploymentArtifact.get("content")));
+        artifact.setVersion(updateLatestVersion(msInstance.getDeploymentArtifactsInfo()));
+        artifact.setStatus(DeploymentArtifactStatus.IN_DEV);
+        artifact.setMsInstanceInfo(createMsInstanceReferenceInfo(msInstance));
+        artifact.setSpecificationInfo(createSpecificationReferenceInfo(msInstance.getActiveSpec()));
+        artifact.setMetadata(createMetadata(user));
+
+        artifact.setFileName(fileNameCreator.createFileName(msInstance, artifact.getVersion()));
+
+        DeploymentArtifact savedDao = deploymentArtifactGateway.save(artifact);
+        artifact.setId(savedDao.getId());
+
+        msInstance.setDeploymentArtifactsInfo(updateMsDeploymentArtifactRef(msInstance.getDeploymentArtifactsInfo(), savedDao.getId()));
+        msInstanceService.updateMsInstance(msInstance);
+
+        return artifact;
+    }
+
+    private int updateLatestVersion(DeploymentArtifactsRef ref) {
+        if(ref == null) return 1;
+        else return  ref.getMostRecentVersion() + 1;
+    }
+
+    private DeploymentArtifactsRef updateMsDeploymentArtifactRef(DeploymentArtifactsRef ref, String deploymentArtifactId) {
+        if(ref == null){
+            ref = new DeploymentArtifactsRef();
+            ref.setMostRecentVersion(1);
+            List<String> deploymentArtifacts = new ArrayList<>();
+            deploymentArtifacts.add(deploymentArtifactId);
+            ref.setDeploymentArtifacts(deploymentArtifacts);
+        }
+        else{
+            ref.setMostRecentVersion(ref.getMostRecentVersion() + 1);
+            List<String> deploymentArtifactList = ref.getDeploymentArtifacts();
+            deploymentArtifactList.add(deploymentArtifactId);
+        }
+        return ref;
+    }
+
+    private Map<String, Object> createMetadata(String user) {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("createdOn", new Date());
+        metadata.put("createdBy", user);
+        return metadata;
+    }
+
+    private Map<String, Object> createSpecificationReferenceInfo(Specification activeSpec) {
+        Map<String, Object> specInfo = new HashMap<>();
+        specInfo.put("id", activeSpec.getId());
+        return specInfo;
+    }
+
+    private MsInstanceInfo createMsInstanceReferenceInfo(MsInstance msInstance) {
+        MsInstanceInfo msInstanceInfo = new MsInstanceInfo();
+        msInstanceInfo.setId(msInstance.getId());
+        msInstanceInfo.setName(msInstance.getName());
+        msInstanceInfo.setRelease(msInstance.getRelease());
+        return msInstanceInfo;
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactStatusChangeHandler.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactStatusChangeHandler.java
new file mode 100644 (file)
index 0000000..48b18bf
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.StatusChangeNotValidException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * A class responsible for handling status changes of Deployment Artifacts
+ */
+@Component
+@Slf4j
+public class DeploymentArtifactStatusChangeHandler {
+
+    @Autowired
+    DeploymentArtifactService deploymentArtifactService;
+
+    /**
+     * setter
+     * @param deploymentArtifactService
+     */
+    public void setDeploymentArtifactService(DeploymentArtifactService deploymentArtifactService) {
+        this.deploymentArtifactService = deploymentArtifactService;
+    }
+
+    /**
+     * handles status changes
+     * @param status
+     * @param deploymentArtifact
+     */
+    public void handleStatusChange(DeploymentArtifactStatus status, DeploymentArtifact deploymentArtifact) {
+        String msInstanceId = deploymentArtifact.getMsInstanceInfo().getId();
+        List<DeploymentArtifact> artifacts = deploymentArtifactService.findByMsInstanceId(msInstanceId);
+        if( status == DeploymentArtifactStatus.DEV_COMPLETE){
+            for(DeploymentArtifact artifact : artifacts){
+                if(artifact.getStatus() == DeploymentArtifactStatus.DEV_COMPLETE){
+                    log.error("Status change is not allowed.");
+                    throw new StatusChangeNotValidException(createValidationErrorMessage(deploymentArtifact));
+                }
+            }
+        }
+        deploymentArtifact.setStatus(status);
+        log.info("Deployment Artifact's status changed successfully.");
+    }
+
+    private String createValidationErrorMessage(DeploymentArtifact artifact) {
+        return String.format( "%s (v%d) for %s - Status change not allowed."
+                + "  Only 1 blueprint can be in the DEV_COMPLETE state.  " +
+               "Change the current DEV_COMPLETE blueprint to NOT_NEEDED or IN_DEV before changing another"
+                + " to DEV_COMPLETE.", artifact.getMsInstanceInfo().getName(),
+                artifact.getVersion(), artifact.getMsInstanceInfo().getRelease());
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceGateway.java
new file mode 100644 (file)
index 0000000..12a510a
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * An interface to interact with MsInstance persistence
+ */
+public interface MsInstanceGateway {
+
+    Optional<MsInstance> findByNameAndRelease(String name, String release);
+
+    Optional<MsInstance> findById(String msInstanceId);
+
+    List<MsInstance> findAll();
+
+    MsInstance save(MsInstance msInstance);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceService.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceService.java
new file mode 100644 (file)
index 0000000..3c28f4d
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceUpdateRequest;
+
+import java.util.List;
+
+/**
+ * An interface to access MsInstance Services
+ */
+public interface MsInstanceService {
+
+    List<MsInstance> getAll();
+
+    MsInstance createMicroserviceInstance(String msName, MsInstanceRequest request);
+
+    MsInstance getMsInstanceById(String id);
+
+    void updateMsInstance(MsInstance msInstance);
+
+    void updateStatusBasedOnDeploymentArtifactsStatuses(String msInstanceId);
+
+    void removeDeploymentArtifactFromMsInstance(DeploymentArtifact deploymentArtifact);
+
+    void updateMicroserviceReference(BaseMicroservice msToBeUpdated);
+
+    MsInstance updateMsInstance(MsInstanceUpdateRequest updateRequest, String msInstanceId);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceServiceImpl.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceServiceImpl.java
new file mode 100644 (file)
index 0000000..e4d5694
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.exceptions.msinstance.MsInstanceAlreadyExistsException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.msinstance.MsInstanceNotFoundException;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstanceStatus;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.MsService;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactService;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationService;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MsInstance Service implementation
+ */
+@Service
+@Setter
+@Slf4j
+public class MsInstanceServiceImpl implements MsInstanceService {
+
+    @Autowired
+    private MsInstanceGateway msInstanceRepository;
+
+    @Autowired
+    private MsService msService;
+
+    @Autowired
+    private MsInstanceStatusChangeHandler msInstanceStatusChangeHandler;
+
+    @Autowired
+    private SpecificationService specificationService;
+
+    @Autowired
+    private DeploymentArtifactService deploymentArtifactService;
+
+    @Override
+    public List<MsInstance> getAll() {
+        return msInstanceRepository.findAll();
+    }
+
+    @Override
+    @Transactional
+    public MsInstance createMicroserviceInstance(String msName, MsInstanceRequest request) {
+        BaseMicroservice microservice = msService.getMicroserviceByName(msName);
+        checkIftheCombinationOfNameAndReleaseIsUnique(request.getName(), request.getRelease());
+        MsInstance msInstance = new MsInstanceCreator(request, microservice).create();
+        MsInstance savedMsInstance = msInstanceRepository.save(msInstance);
+        msService.saveMsInstanceReferenceToMs(microservice, savedMsInstance);
+        return savedMsInstance;
+    }
+
+    private void checkIftheCombinationOfNameAndReleaseIsUnique(String name, String release) {
+        if (msInstanceRepository.findByNameAndRelease(name, release).isPresent())
+            throw new MsInstanceAlreadyExistsException();
+    }
+
+    @Override
+    public MsInstance getMsInstanceById(String id) {
+        return msInstanceRepository.findById(id).orElseThrow(() ->
+                new MsInstanceNotFoundException(String.format("Ms Instance with id %s not found", id)));
+    }
+
+    @Override
+    public void updateMsInstance(MsInstance msInstance) {
+        log.info("Saving the msInstance {} to database..", msInstance);
+        if(msInstance != null) msInstanceRepository.save(msInstance);
+    }
+
+    @Override
+    public void updateStatusBasedOnDeploymentArtifactsStatuses(String msInstanceId) {
+        MsInstance msInstance = getMsInstanceById(msInstanceId);
+        msInstanceStatusChangeHandler.updateStatusBasedOnDeploymentArtifactsStatuses(msInstance);
+        updateMsInstance(msInstance);
+    }
+
+    @Override
+    @Transactional
+    public void removeDeploymentArtifactFromMsInstance(DeploymentArtifact deploymentArtifact) {
+        MsInstance msInstance = getMsInstanceById(deploymentArtifact.getMsInstanceInfo().getId());
+        removeDeploymentArtifactReferenceFromMsInstance(msInstance, deploymentArtifact.getId());
+        msInstanceStatusChangeHandler.updateStatusBasedOnDeploymentArtifactsStatuses(msInstance);
+        updateMsInstance(msInstance);
+    }
+
+    @Override
+    //TODO: update msInstanceReference in specification and deployment artifact
+    public void updateMicroserviceReference(BaseMicroservice microservice) {
+        List<Map<String, String>> msInstanceRefs = microservice.getMsInstances();
+        for(Map<String, String> ref : msInstanceRefs){
+            MsInstance msInstance = getMsInstanceById(ref.get("id"));
+            msInstance.setName(microservice.getName());
+            msInstance.getMsInfo().put("name", microservice.getName());
+            cascadeUpdates(msInstance);
+            msInstanceRepository.save(msInstance);
+        }
+    }
+
+    @Override
+    @Transactional
+    public MsInstance updateMsInstance(MsInstanceUpdateRequest updateRequest, String msInstanceId) {
+        MsInstance msInstance = getMsInstanceById(msInstanceId);
+        updateRelease(updateRequest, msInstance);
+        updateVersion(updateRequest, msInstance);
+        updateMetadata(updateRequest, msInstance);
+        cascadeUpdates(msInstance);
+        return msInstanceRepository.save(msInstance);
+    }
+
+    private void cascadeUpdates(MsInstance msInstance) {
+        specificationService.updateMsInstanceRef(msInstance);
+        deploymentArtifactService.updateMsInstanceRef(msInstance);
+        msService.updateMsInstanceRef(msInstance);
+    }
+
+    private void updateMetadata(MsInstanceUpdateRequest updateRequest, MsInstance msInstance) {
+        if(updateRequest.getMetadata() != null){
+            msInstance.getMetadata().putAll(updateRequest.getMetadata());
+        }
+
+        msInstance.getMetadata().put("updatedOn", new Date());
+        msInstance.getMetadata().put("updatedBy", updateRequest.getUser());
+    }
+
+    private void updateVersion(MsInstanceUpdateRequest updateRequest, MsInstance msInstance) {
+        if(updateRequest.getVersion() != null){
+            msInstance.setVersion(updateRequest.getVersion());
+        }
+    }
+
+    private void updateRelease(MsInstanceUpdateRequest updateRequest, MsInstance msInstance) {
+        if(updateRequest.getRelease() != null) {
+            if(!updateRequest.getRelease().equals(msInstance.getRelease()))
+                checkIftheCombinationOfNameAndReleaseIsUnique(msInstance.getName(), updateRequest.getRelease());
+            msInstance.setRelease(updateRequest.getRelease());
+        }
+    }
+
+    private void removeDeploymentArtifactReferenceFromMsInstance(MsInstance msInstance, String deploymentArtifactId) {
+        if(msInstance.getDeploymentArtifactsInfo() != null){
+            List<String> refIds = msInstance.getDeploymentArtifactsInfo().getDeploymentArtifacts();
+            refIds.remove(deploymentArtifactId);
+        }
+    }
+
+    private class MsInstanceCreator {
+        private MsInstanceRequest request;
+        private BaseMicroservice microserviceDAO;
+
+        MsInstanceCreator(MsInstanceRequest request, BaseMicroservice microserviceDAO) {
+            this.request = request;
+            this.microserviceDAO = microserviceDAO;
+        }
+
+        MsInstance create() {
+            //prepare MsInstance from the request
+            return MsInstance.builder()
+                    .name(request.getName())
+                    .release(request.getRelease())
+                    .status(MsInstanceStatus.NEW)
+                    .version(request.getVersion())
+                    .msInfo(getMsReference(microserviceDAO))
+                    .metadata(getMetadata(request))
+                    .build();
+        }
+
+        private Map<String, Object> getMsReference(BaseMicroservice microserviceDAO) {
+            Map<String,Object> msInfo = new HashMap<>();
+            msInfo.put("id", microserviceDAO.getId());
+            msInfo.put("name", microserviceDAO.getName());
+            msInfo.put("tag", microserviceDAO.getTag());
+            return msInfo;
+        }
+
+        private Map<String, Object> getMetadata(MsInstanceRequest request) {
+            Map<String, Object> metadata = request.getMetadata();
+            metadata.put("createdBy", request.getUser());
+            metadata.put("createdOn", new Date());
+            return metadata;
+        }
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceStatusChangeHandler.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/microserviceinstance/MsInstanceStatusChangeHandler.java
new file mode 100644 (file)
index 0000000..bc26fab
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.microserviceinstance;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstanceStatus;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * A class responsible for handling status changes of Ms Instances
+ */
+@Component
+@Slf4j
+public class MsInstanceStatusChangeHandler {
+
+    @Autowired
+    private MsInstanceService msInstanceService;
+
+    @Autowired
+    private DeploymentArtifactService deploymentArtifactService;
+
+    public void setMsInstanceService(MsInstanceService msInstanceService) {
+        this.msInstanceService = msInstanceService;
+    }
+
+    public void setDeploymentArtifactService(DeploymentArtifactService deploymentArtifactService) {
+        this.deploymentArtifactService = deploymentArtifactService;
+    }
+
+    public void updateStatusBasedOnDeploymentArtifactsStatuses(MsInstance msInstance) {
+        log.info("Checking if any Status change required for msInstance {}...", msInstance);
+        List<DeploymentArtifact> artifacts = deploymentArtifactService.findByMsInstanceId(msInstance.getId());
+        MsInstanceStatus newStatus = getValidStatusBasedOnArtifacts(artifacts);
+        msInstance.setStatus(newStatus);
+        log.info("Changed Status to {}", newStatus);
+    }
+
+    private MsInstanceStatus getValidStatusBasedOnArtifacts(List<DeploymentArtifact> artifacts) {
+        if(atLeastOneArtifactHasDevCompleteStatus(artifacts)){
+            return MsInstanceStatus.DEV_COMPLETE;
+        }
+        return MsInstanceStatus.IN_DEV;
+    }
+
+    private boolean atLeastOneArtifactHasDevCompleteStatus(List<DeploymentArtifact> artifacts) {
+         return artifacts
+                 .stream()
+                 .anyMatch(artifact -> artifact.getStatus() == DeploymentArtifactStatus.DEV_COMPLETE);
+    }
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationGateway.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationGateway.java
new file mode 100644 (file)
index 0000000..5fcfbb1
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.specification;
+
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * An interface to interact with Specification persistence
+ */
+@Repository
+public interface SpecificationGateway{
+
+    List<Specification> getSpecificationByMsInstanceId(String id);
+
+    Specification save(Specification newSpec);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationService.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationService.java
new file mode 100644 (file)
index 0000000..33724ac
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.specification;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+
+import java.util.List;
+
+/**
+ * An interface to access Specification Services
+ */
+public interface SpecificationService {
+
+    List<Specification> getAllSpecsByMsInstanceId(String id);
+
+    Specification createSpecification(String msInstanceId, SpecificationRequest request);
+
+    void updateMsInstanceRef(MsInstance msInstance);
+}
+
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationServiceImpl.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationServiceImpl.java
new file mode 100644 (file)
index 0000000..7869801
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.specification;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstanceStatus;
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.model.specification.SpecificationStatus;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import lombok.Setter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Specification Service implementation
+ */
+@Service
+@Setter
+public class SpecificationServiceImpl implements SpecificationService {
+
+    @Autowired
+    private SpecificationGateway specificationGateway;
+
+    @Autowired
+    private MsInstanceService msInstanceService;
+
+    @Autowired
+    private SpecificationValidatorService specificationValidatorService;
+
+    /**
+     * Lists all Ms Instances
+     * @param id
+     * @return
+     */
+    @Override
+    public List<Specification> getAllSpecsByMsInstanceId(String id) {
+        return specificationGateway.getSpecificationByMsInstanceId(id);
+    }
+
+    /**
+     * creates a Specification
+     * @param msInstanceId
+     * @param request
+     * @return
+     */
+    @Override
+    @Transactional
+    public Specification createSpecification(String msInstanceId, SpecificationRequest request) {
+        MsInstance msInstance = msInstanceService.getMsInstanceById(msInstanceId);
+        specificationValidatorService.validateSpecForRelease(request, msInstance.getRelease());
+        Specification newSpec = createSpecification(request, msInstance);
+        makePreviousSpecInactive(msInstance);
+        Specification savedSpec = specificationGateway.save(newSpec);
+        updateMsInstance(msInstance, savedSpec);
+        return savedSpec;
+    }
+
+    private Specification createSpecification(SpecificationRequest request, MsInstance msInstance) {
+        return Specification.builder()
+                .status(SpecificationStatus.ACTIVE)
+                .specContent(request.getSpecContent())
+                .policyJson(request.getPolicyJson())
+                .type(request.getType())
+                .metadata(getMetadata(request))
+                .msInstanceInfo(buildMsInstanceInfo(msInstance))
+                .build();
+    }
+
+    private void updateMsInstance(MsInstance msInstance, Specification savedSpecification) {
+        msInstance.setActiveSpec(savedSpecification);
+        msInstance.setStatus(MsInstanceStatus.IN_DEV);
+        msInstanceService.updateMsInstance(msInstance);
+    }
+
+    private void makePreviousSpecInactive(MsInstance msInstance) {
+        if (msInstance.getActiveSpec() != null) {
+            msInstance.getActiveSpec().setStatus(SpecificationStatus.INACTIVE);
+            specificationGateway.save(msInstance.getActiveSpec());
+        }
+    }
+
+    private Map<String, Object> getMetadata(SpecificationRequest request) {
+        Map<String, Object> metadata = request.getMetadata();
+        metadata.put("createdBy", request.getUser());
+        metadata.put("createdOn", new Date());
+        return metadata;
+    }
+
+    private Map<String, Object> buildMsInstanceInfo(MsInstance msInstance) {
+        Map<String, Object> msInstanceInfo = new HashMap<>();
+        msInstanceInfo.put("id", msInstance.getId());
+        msInstanceInfo.put("name", msInstance.getName());
+        msInstanceInfo.put("release", msInstance.getRelease());
+        return msInstanceInfo;
+    }
+
+    /**
+     * Updates a MsInstance reference in a Specification record
+     * @param msInstance
+     */
+    @Override
+    @Transactional
+    public void updateMsInstanceRef(MsInstance msInstance) {
+        List<Specification> specifications = getAllSpecsByMsInstanceId(msInstance.getId());
+        specifications.forEach((specification) ->{
+            specification.getMsInstanceInfo().put("name", msInstance.getName());
+            specification.getMsInstanceInfo().put("release", msInstance.getRelease());
+            specificationGateway.save(specification);
+        });
+    }
+
+
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationValidationStratergy.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationValidationStratergy.java
new file mode 100644 (file)
index 0000000..58eff19
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.specification;
+
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+
+/**
+ * Abstraction for Specification Validation.
+ */
+public interface SpecificationValidationStratergy {
+    public void validate(SpecificationRequest specificationRequest, String release);
+}
diff --git a/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationValidatorService.java b/mod2/catalog-service/src/main/java/org/onap/dcaegen2/platform/mod/web/service/specification/SpecificationValidatorService.java
new file mode 100644 (file)
index 0000000..6d10aee
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.specification;
+
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * A service to validate specification
+ */
+@Service
+public class SpecificationValidatorService {
+
+    @Autowired
+    SpecificationValidationStratergy specValidator;
+
+    public void validateSpecForRelease(SpecificationRequest specificationRequest, String release) {
+        specValidator.validate(specificationRequest, release);
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mod2/catalog-service/src/main/resources/application.properties b/mod2/catalog-service/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..1f20c6a
--- /dev/null
@@ -0,0 +1,24 @@
+#
+# ============LICENSE_START=======================================================
+#  org.onap.dcae
+#  ================================================================================
+#  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+#
+
+#add connection to mongo db once its up and running
+spring.data.mongodb.host=mongo_db
+spring.data.mongodb.port=27017
+spring.data.mongodb.database=dcae_mod
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/BaseMsObjectMother.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/BaseMsObjectMother.java
new file mode 100644 (file)
index 0000000..fc57442
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsLocation;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsStatus;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsType;
+import org.onap.dcaegen2.platform.mod.model.common.AuditFields;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceUpdateRequest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.*;
+
+public class BaseMsObjectMother {
+
+    public static final String BASE_MS_NAME = "ms-1";
+    public static final String BASE_MS_ID = "id123";
+    public static final BaseMsType BASE_MS_TYPE = BaseMsType.TICK;
+    public static final BaseMsLocation LOCATION = BaseMsLocation.CENTRAL;
+    public static final String NAMESPACE = "sam.collector.namespace";
+    public static final String NOTE = "Sample Note";
+    public static final String LABEL_1 = "mylabel1";
+    public static final String LABEL_2 = "mylabel2";
+    public static final String USER = "abc123";
+    private static final String BASE_MS_TAG = "sample-ms-tag" ;
+    private static final String BASE_MS_SERVICE_NAME = "sample-core";
+
+
+    public static MicroserviceCreateRequest createMockMsRequest() {
+        Map<String, Object> metadata = new HashMap();
+        metadata.put("notes", NOTE);
+        metadata.put("labels", Arrays.asList(LABEL_1, LABEL_2));
+
+        MicroserviceCreateRequest request = new MicroserviceCreateRequest();
+        request.setName(BASE_MS_NAME);
+        request.setTag(BASE_MS_TAG);
+        request.setServiceName(BASE_MS_SERVICE_NAME);
+        request.setType(BASE_MS_TYPE);
+        request.setLocation(LOCATION);
+        request.setNamespace(NAMESPACE);
+        request.setMetadata(metadata);
+        request.setUser(USER);
+
+        return request;
+    }
+
+    public static BaseMicroservice createMockMsObject() {
+        BaseMicroservice microservice = new BaseMicroservice();
+        microservice.setId(BASE_MS_ID);
+        microservice.setName(BASE_MS_NAME);
+        microservice.setServiceName(BASE_MS_SERVICE_NAME);
+        microservice.setTag(BASE_MS_TAG);
+        microservice.setType(BASE_MS_TYPE);
+        microservice.setLocation(LOCATION);
+        microservice.setNamespace(NAMESPACE);
+        microservice.setStatus(BaseMsStatus.ACTIVE);
+        microservice.setMetadata(prepareAuditFields());
+        microservice.setMsInstances(createMsInstanceReferences());
+        return microservice;
+    }
+
+    private static List<Map<String, String>> createMsInstanceReferences() {
+        List<Map<String, String>> msInstanceRefs = new ArrayList<>();
+        Map<String, String> msInstance_1 = new HashMap<>();
+        msInstance_1.put("name", BASE_MS_NAME);
+        msInstance_1.put("id", "instance-1");
+        Map<String, String> msInstance_2 = new HashMap<>();
+        msInstance_2.put("name", BASE_MS_NAME);
+        msInstance_2.put("id", "instance-2");
+        msInstanceRefs.add(msInstance_1);
+        msInstanceRefs.add(msInstance_2);
+        return msInstanceRefs;
+    }
+
+
+    public static AuditFields prepareAuditFields() {
+        return AuditFields.builder()
+                .createdBy(USER)     // prepared by core
+                .createdOn(new Date(12323132L))
+                .updatedBy(USER)
+                .updatedOn(new Date(12323133L))
+                .notes(NOTE)
+                .labels(Arrays.asList(LABEL_1, LABEL_2))
+                .build();
+
+    }
+
+    public static MicroserviceUpdateRequest createUpdateMsRequest() {
+        MicroserviceUpdateRequest updateRequest = new MicroserviceUpdateRequest();
+        updateRequest.setName("updatedName");
+        updateRequest.setLocation(BaseMsLocation.EDGE);
+        updateRequest.setServiceName("updated-core-name");
+        updateRequest.setNamespace("updatedNameSpace");
+        updateRequest.setType(BaseMsType.ANALYTIC);
+        updateRequest.setUser("updater");
+
+        Map<String, Object> metadata = new HashMap();
+        metadata.put("notes", "updatedNote");
+        metadata.put("labels", Arrays.asList("updatedLabel1", "updatedLabel2"));
+        updateRequest.setMetadata(metadata);
+        return updateRequest;
+    }
+
+    public static String asJsonString(final Object object) {
+        try {
+            return new ObjectMapper().writeValueAsString(object);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/DeploymentArtifactObjectMother.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/DeploymentArtifactObjectMother.java
new file mode 100644 (file)
index 0000000..da52624
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.MsInstanceInfo;
+
+import java.util.*;
+
+import static org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother.USER;
+
+public class DeploymentArtifactObjectMother {
+
+
+    public static final String BLUEPRINT_FILENAME = "hello-world-k8s-blueprint.yaml";
+    public static final String BLUEPRINT_CONTENT = "\\n#Basic java app to print out at&t buzzwords\\n#1.0" +
+            ".0\\n#\\n---\\" + "ntosca_definitions_version: cloudify_dsl_1_3\\nimports:\\n- http://www.getcloudify" +
+            ".org/spec/cloudify/4.4/types" + ".yaml\\n- http://dockercentral.it.att" +
+            ".com:8093/nexus/repository/rawcentral/com.att.dcae.controller/type_files/" + "k8splugin/1.7.4/node-type" +
+            ".yaml\\n- http://dockercentral.it.att.com:8093/nexus/repository/rawcentral/com.att.d" + "cae.controller" +
+            "/type_files/relationship/2006001.1.0/types.yaml\\n- http://dockercentral.it.att.com:8093/nexus/" +
+            "repository/rawcentral/com.att.dcae.controller/type_files/cloudifydmaapplugin/1.4.10/node-type.yaml\\n- " +
+            "http:/" + "/dockercentral.it.att.com:8093/nexus/repository/rawcentral/com.att.dcae" +
+            ".controller/type_files/dcaepolicyplugi" + "n/2.3.3/node-type.yaml\\n- http://dockercentral.it.att" +
+            ".com:8093/nexus/repository/rawcentral/com.att.dcae.cont" + "roller/type_files/pgaas/0.3.2/pgaas_types" +
+            ".yaml\\ninputs:\\n  ConsulTest1:\\n    type: string\\n    description" + ": test description\\n    " +
+            "default: 'TEST1'\\n  ConsulTest2:\\n    type: string\\n    description: test description\\n    default: " +
+            "'TEST2'\\n  aaf_cert_directory:\\n    type: string\\n    default: '/opt/app/aafcertman'\\n    " +
+            "description: directory location for the aaf-tls certs\\n  additionalsans:\\n    type: string\\n    " +
+            "default: ''\\n    description: additional sans (string)\\n  annotations:\\n    default: {}\\n  " +
+            "app_name:\\n    type: string\\n    default: 'dcae'\\n    description: This is used to generateForRelease different" +
+            " secret code for DCAE or D2A based\\n      on Tosca or Helm based BP\\n  dcae_service_location:\\n    " +
+            "type: string\\n    description: Docker host override for docker bps (string)\\n  " +
+            "dti_sidecar_cpu_limit:\\n    type: string\\n    default: '250m'\\n    description: cpu limit for " +
+            "deployment (string)\\n  dti_sidecar_cpu_request:\\n    type: string\\n    default: '250m'\\n    " +
+            "description: cpu requested for deployment (string)\\n  dti_sidecar_image:\\n    type: string\\n    " +
+            "default: 'dockercentral.it.att.com:5100/com.att.dcae.controller/dcae-controller-sidecar:19.11-001'\\n   " +
+            " description: dti side car image for dti (string)\\n  dti_sidecar_memory_limit:\\n    type: string\\n   " +
+            " default: '128Mi'\\n    description: memory limit for deployment (string)\\n  " +
+            "dti_sidecar_memory_request:\\n    type: string\\n    default: '128Mi'\\n    description: memory " +
+            "requested for deployment (string)\\n  dti_sidecar_port:\\n    type: string\\n    default: ''\\n    " +
+            "description: Port for the side car (string)\\n  hello-buzzword_cpu_limit:\\n    type: string\\n    " +
+            "default: '250m'\\n    description: cpu limit for deployment (string)\\n  hello-buzzword_cpu_request:\\n " +
+            "   type: string\\n    default: '250m'\\n    description: cpu requested for deployment (string)\\n  " +
+            "hello-buzzword_memory_limit:\\n    type: string\\n    default: '128Mi'\\n    description: memory limit " +
+            "for deployment (string)\\n  hello-buzzword_memory_request:\\n    type: string\\n    default: '128Mi'\\n " +
+            "   description: memory requested for deployment (string)\\n  idns_fqdn:\\n    type: string\\n    " +
+            "default: ''\\n    description: The idns you will be using for your deployment (string)\\n  image:\\n    " +
+            "type: string\\n    default: 'test-image-uri'\\n    description: The docker image for your microservice " +
+            "(string)\\n  namespace:\\n    type: string\\n  replicas:\\n    type: integer\\n    default: 1\\n    " +
+            "description: The number of replicas for your kubernetes deployment (integer)\\n  " +
+            "service_component_name_override:\\n    type: string\\n    default: 'hello-buzzword'\\n    description: " +
+            "Unique identifier for your deployment (string)\\n  use_aaf_tls:\\n    type: boolean\\n    default: " +
+            "false\\n    description: To use or not use the aaf section (boolean)\\n  use_dti_info:\\n    type: " +
+            "boolean\\n    default: true\\n    description: Flag to use or not use dti (boolean)\\nnode_templates:\\n" +
+            "  hello-buzzword_hello-buzzword:\\n    type: dcae.nodes.ContainerizedServiceComponent\\n    " +
+            "properties:\\n      application_config:\\n        services_calls: []\\n        streams_publishes: {}\\n " +
+            "       streams_subscribes: {}\\n        ConsulTest1:\\n          get_input: ConsulTest1\\n        " +
+            "ConsulTest2:\\n          get_input: ConsulTest2\\n      docker_config:\\n        healthcheck:\\n        " +
+            "  interval: 180s\\n          timeout: 30s\\n          script: \\\"true\\\"\\n          type: docker\\n  " +
+            "      livehealthcheck:\\n          interval: 180s\\n          timeout: 30s\\n          script: " +
+            "\\\"true\\\"\\n          type: docker\\n        reconfigs:\\n          dti: dti/test-script\\n          " +
+            "app_reconfig: /app-reconfig/test-script\\n        env:\\n        - name: DTI_DATA_DIR\\n          value:" +
+            " /dtidata\\n        - name: KUBE_CLUSTER_FQDN\\n          value: {get_secret: " +
+            "kc-kubernetes_master_ip}\\n      image:\\n        get_input: image\\n      location_id:\\n        " +
+            "get_input: dcae_service_location\\n      service_component_type: hello-buzzword\\n      replicas:\\n    " +
+            "    get_input: replicas\\n      service_component_name_override:\\n        concat:\\n        - " +
+            "get_secret: location_id\\n        - '-'\\n        - get_input: service_component_name_override\\n      " +
+            "k8s_controller_type: statefulset\\n      configuration:\\n        file_content:\\n          apiVersion: " +
+            "v1\\n          clusters:\\n          - name: default-cluster\\n            cluster:\\n              " +
+            "server:\\n                concat:\\n                - https://\\n                - get_secret: " +
+            "kc-kubernetes_master_ip\\n                - ':'\\n                - get_secret: " +
+            "kc-kubernetes_master_port\\n              insecure-skip-tls-verify: true\\n          contexts:\\n       " +
+            "   - name: default-context\\n            context:\\n              cluster: default-cluster\\n           " +
+            "   namespace:\\n                get_input: namespace\\n              user: default-user\\n          " +
+            "kind: Config\\n          preferences: {}\\n          users:\\n          - name: default-user\\n         " +
+            "   user:\\n              token:\\n                get_secret:\\n                  concat:\\n            " +
+            "      - get_input: app_name\\n                  - -mechid-k8s-token\\n          current-context: " +
+            "default-context\\n      resource_config:\\n        limits:\\n          cpu:\\n            get_input: " +
+            "hello-buzzword_cpu_limit\\n          memory:\\n            get_input: hello-buzzword_memory_limit\\n    " +
+            "    requests:\\n          cpu:\\n            get_input: hello-buzzword_cpu_request\\n          " +
+            "memory:\\n            get_input: hello-buzzword_memory_request\\n      aaf_tls_info:\\n        " +
+            "use_aaf_tls:\\n          get_input: use_aaf_tls\\n        cert_directory:\\n          get_input: " +
+            "aaf_cert_directory\\n        image: dockercentral.it.att.com:5100/com.att.ecompcntr" +
+            ".public/ecompc-aaf-init-container:1.0.2\\n        env:\\n        - name: NAMESPACE\\n          " +
+            "valueFrom:\\n            fieldRef:\\n              fieldPath: metadata.namespace\\n        - name: " +
+            "deployer_id\\n          valueFrom:\\n            secretKeyRef:\\n              name:\\n                " +
+            "concat:\\n                - get_input: namespace\\n                - -cert-secret\\n              key: " +
+            "deployerid\\n        - name: deployer_pass\\n          valueFrom:\\n            secretKeyRef:\\n        " +
+            "      name:\\n                concat:\\n                - get_input: namespace\\n                - " +
+            "-cert-secret\\n              key: deployerpass\\n        - name: cert_id\\n          valueFrom:\\n      " +
+            "      secretKeyRef:\\n              name:\\n                concat:\\n                - get_input: " +
+            "namespace\\n                - -cert-secret\\n              key: certid\\n        - name: cm_url\\n      " +
+            "    valueFrom:\\n            secretKeyRef:\\n              name:\\n                concat:\\n           " +
+            "     - get_input: namespace\\n                - -cert-secret\\n              key: cmurl\\n        - " +
+            "name: idns_fqdn\\n          value:\\n            get_input: idns_fqdn\\n        - name: " +
+            "app_service_names\\n          value:\\n            concat:\\n            - get_secret: location_id\\n   " +
+            "         - '-'\\n            - get_input: service_component_name_override\\n        args:\\n        - " +
+            "place\\n        - cmtemplate\\n        - -idnsfqdn=$(idns_fqdn)\\n        - -cmurl=$(cm_url)\\n        -" +
+            " -deployerid=$(deployer_id)\\n        - -deployerpass=$(deployer_pass)\\n        - -certid=$(cert_id)\\n" +
+            "        - -namespace=$(NAMESPACE)\\n        - -services=$(app_service_names)\\n        - concat:\\n     " +
+            "     - -additionalsans=\\n          - get_input: additionalsans\\n        use_aaf_tls_renewal: true\\n  " +
+            "      renewal_args:\\n        - renew\\n        - -idnsfqdn=$(idns_fqdn)\\n        - -cmurl=$(cm_url)\\n" +
+            "        resource_config:\\n          limits:\\n            cpu: 250m\\n            memory: 256Mi\\n     " +
+            "     requests:\\n            cpu: 100m\\n            memory: 256Mi\\n      annotations:\\n        " +
+            "get_input: annotations\\n      dti_info:\\n        image:\\n          get_input: dti_sidecar_image\\n   " +
+            "     use_dti_info:\\n          get_input: use_dti_info\\n        healthcheck:\\n          interval: " +
+            "90s\\n          timeout: 60s\\n          type: https\\n          endpoint: /healthcheck\\n        " +
+            "livehealthcheck:\\n          interval: 90s\\n          timeout: 60s\\n          type: https\\n          " +
+            "endpoint: /healthcheck\\n        dtidata_directory: /dtidata\\n        resource_config:\\n          " +
+            "limits:\\n            cpu:\\n              get_input: dti_sidecar_cpu_limit\\n            memory:\\n    " +
+            "          get_input: dti_sidecar_memory_limit\\n          requests:\\n            cpu:\\n              " +
+            "get_input: dti_sidecar_cpu_request\\n            memory:\\n              get_input: " +
+            "dti_sidecar_memory_request\\n        env:\\n        - name: DTI_DATA_DIR\\n          value: /dtidata\\n " +
+            "       - name: KUBE_CLUSTER_FQDN\\n          value: {get_secret: kc-kubernetes_master_ip}\\n        - " +
+            "name: KUBE_PROXY_FQDN\\n          value: {get_secret: kube_proxy_fqdn}\\n        - name: POD_SVC_PORT\\n" +
+            "          value: '9999'\\n        ports:\\n        - concat:\\n          - '9999:'\\n          - " +
+            "get_input: dti_sidecar_port\\n    relationships: []";
+
+    public static final String SPEC_FILE_AS_STRING = String.format("{\r\n\t\"self\": {\r\n\t\t\"component_type\": " +
+            "\"docker\",\r\n\t\t\"description\": \"Basic java app to print out at&t buzzwords\",\r\n\t\t\"name\": " +
+            "\"hello-buzzword\",\r\n\t\t\"version\": \"1.0.0\"\r\n\t},\r\n\t\r\n\t\"services\": {\r\n\t\t\"calls\": " +
+            "[],\r\n\t\t\"provides\": []\r\n\t},\r\n\t\"streams\": {\r\n\t\t\"publishes\": [],\r\n\t\t\"subscribes\":" +
+            " []\r\n\t},\r\n\t\"parameters\": [\r\n\t\t{\r\n            \"name\": \"ConsulTest1\",\r\n            " +
+            "\"value\": \"TEST1\",\r\n            \"description\": \"Test consul output\"," +
+            "\r\n\t\t\t\"sourced_at_deployment\": true,\r\n\t\t\t\"designer_editable\": true," +
+            "\r\n\t\t\t\"policy_editable\": false,\r\n\t\t\t\"type\": \"string\" ,\r\n\t\t\t\"description\": \"test " +
+            "description\"        \r\n        },\r\n        {\r\n            \"name\": \"ConsulTest2\",\r\n          " +
+            "  \"value\": \"TEST2\",\r\n\t\t\t\"sourced_at_deployment\": true,\r\n\t\t\t\"designer_editable\": true," +
+            "\r\n\t\t\t\"policy_editable\": false,\r\n\t\t\t\"type\": \"string\",\r\n\t\t\t\"description\": \"test " +
+            "description\"    \r\n        }\r\n       \r\n\t],\r\n\r\n\t\"auxilary\": {\r\n\t\t\"healthcheck\": " +
+            "{\r\n\t\t\t\"type\": \"docker\",\r\n        \t\"script\": \"true\",\r\n        \t\"timeout\": \"30s\"," +
+            "\r\n        \t\"interval\": \"180s\"\r\n\t\t},\r\n\t\t\"livehealthcheck\": {\r\n\t\t\t\"type\": " +
+            "\"docker\",\r\n        \t\"script\": \"true\",\r\n        \t\"timeout\": \"30s\",\r\n        " +
+            "\t\"interval\": \"180s\"\r\n\t\t},\r\n\t\t\"reconfigs\": {\r\n\t\t\t\"app_reconfig\" : " +
+            "\"/app-reconfig/test-script\",\r\n\t\t\t\"dti\" : \"dti/test-script\"}}," +
+            "\r\n\t\"artifacts\": [{\r\n\t\t\"type\": \"docker image\",\r\n\t\t\"uri\": " +
+            "\"test-image-uri\"\r\n\t}]\r\n}");
+
+    public static DeploymentArtifact createDeploymentArtifactDAO(DeploymentArtifactStatus status) {
+        DeploymentArtifact artifact = new DeploymentArtifact();
+        artifact.setId("id-123");
+        artifact.setFileName("helloworld-k8s-blueprint.yaml");
+        artifact.setContent("some " + "yaml content");
+        artifact.setStatus(status);
+        artifact.setVersion(1);
+        artifact.setMetadata(createMetaData());
+        artifact.setMsInstanceInfo(createMsInstanceInfo());
+        artifact.setSpecificationInfo(createSpecificationInfo());
+
+       return artifact;
+    }
+
+    private static Map<String, Object> createSpecificationInfo() {
+        Map<String, Object> msInstanceInfo = new HashMap<>();
+        msInstanceInfo.put("id", "id-123");
+        return msInstanceInfo;
+    }
+
+    private static MsInstanceInfo createMsInstanceInfo() {
+        MsInstanceInfo msInstanceInfo = new MsInstanceInfo();
+        msInstanceInfo.setId(MsInstanceObjectMother.MS_INSTANCE_ID);
+        msInstanceInfo.setName(MsInstanceObjectMother.MS_INSTANCE_NAME);
+        msInstanceInfo.setRelease(MsInstanceObjectMother.RELEASE);
+        return msInstanceInfo;
+    }
+
+    private static Map<String, Object> createMetaData() {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("createdBy", USER);
+        metadata.put("createdOn", "someDate");
+        metadata.put("notes", "This is a test Deployment Artifact");
+        metadata.put("labels", Arrays.asList("hello", "world"));
+        return metadata;
+    }
+
+    public static Map<String, Object> createBlueprintResponse() {
+        Map<String, Object> blueprintMap = new HashMap<>();
+        blueprintMap.put("fileName", BLUEPRINT_FILENAME);
+        blueprintMap.put("content", BLUEPRINT_CONTENT); return blueprintMap;
+    }
+
+    public static Map<String, Object> createToolboxBlueprintResponse() {
+        Map<String, Object> blueprintResponseMap = new HashMap<>();
+        blueprintResponseMap.put("blueprint_name", "hello-buzzword-eom-k8s");
+        blueprintResponseMap.put("blueprint_content", BLUEPRINT_CONTENT);
+        blueprintResponseMap.put("componentSpecValidated", true);
+        return blueprintResponseMap;
+    }
+
+    public static List<DeploymentArtifact> createMockDeploymentArtifactsWithDifferentStatuses
+            (boolean devCompleteRequire) {
+        DeploymentArtifact d1;
+        if(devCompleteRequire){
+            d1  = createDeploymentArtifactDAO(DeploymentArtifactStatus.DEV_COMPLETE);
+        }else {
+            d1  = createDeploymentArtifactDAO(DeploymentArtifactStatus.NOT_NEEDED);
+        }
+        DeploymentArtifact d2 = createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        return new ArrayList<>(Arrays.asList(d1, d2));
+    }
+
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/MsInstanceObjectMother.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/MsInstanceObjectMother.java
new file mode 100644 (file)
index 0000000..976e31c
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+import org.onap.dcaegen2.platform.mod.model.specification.DeploymentType;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.DeploymentArtifactsRef;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstanceStatus;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceRequest;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class MsInstanceObjectMother {
+
+    public static final String MS_INSTANCE_NAME = "ms-instance-1";
+    public static final String MS_INSTANCE_ID = "id-123";
+    public static final String RELEASE = "2002";
+    public static final String VERSION = "1.1";
+    public static final String USER = "user-1";
+    public static final String BASE_MS_TAG = "ms-instance-1-tag";
+    public static final String SCRUMLEAD = "Sam";
+    public static final String SYSTEMSENGINEER = "John";
+
+    public static MsInstanceRequest getMsInstanceMockRequest() {
+        Map<String, Object> metadataFromRequest = buildMockMetadataForRequest();
+
+        MsInstanceRequest request = MsInstanceRequest.builder()
+                .name(MS_INSTANCE_NAME)
+                .release(RELEASE)
+                .version(VERSION)
+                .user(USER)
+                .metadata(metadataFromRequest)
+                .build();
+
+        return request;
+    }
+
+    private static Map<String, Object> buildMockMetadataForRequest() {
+        Map<String, Object> metadataFromRequest = new HashMap<>();
+        metadataFromRequest.put("pstDueDate", "14-04-2020");
+        metadataFromRequest.put("pstDueIteration", "1.2");
+        metadataFromRequest.put("eteDueDate", "21-05-2020");
+        metadataFromRequest.put("eteDueIteration", "1.3");
+        metadataFromRequest.put("scrumLead", SCRUMLEAD);
+        metadataFromRequest.put("systemsEngineer", SYSTEMSENGINEER);
+        return metadataFromRequest;
+    }
+
+
+    public static MsInstance createMsInstance() {
+        Map<String, Object> metadataFromResponse = buildMockMetadataForRequest().entrySet()
+                .stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+        metadataFromResponse.put("createdOn", "currentDate");
+        metadataFromResponse.put("createdBy", USER);
+        metadataFromResponse.put("scrumLead", SCRUMLEAD);
+        metadataFromResponse.put("systemsEngineer", SYSTEMSENGINEER);
+
+        Map<String, Object> msInfo = new HashMap<>();
+        msInfo.put("id", BaseMsObjectMother.BASE_MS_ID);
+        msInfo.put("name", BaseMsObjectMother.BASE_MS_NAME);
+        msInfo.put("tag", BASE_MS_TAG);
+
+        MsInstance msInstance = MsInstance.builder()
+                .id(MS_INSTANCE_ID)
+                .name(MS_INSTANCE_NAME)
+                .release(RELEASE)
+                .version(VERSION)
+                .status(MsInstanceStatus.NEW)
+                .metadata(metadataFromResponse)
+                .msInfo(msInfo)
+                .activeSpec(SpecificationObjectMother.getMockSpecification(DeploymentType.DOCKER))
+                .build();
+
+        return msInstance;
+    }
+
+    public static MsInstance getMsInstanceWithExistingDeploymentArtifactRef() {
+        MsInstance msInstance = createMsInstance();
+
+        DeploymentArtifactsRef deploymentArtifactRef = new DeploymentArtifactsRef();
+        deploymentArtifactRef.setMostRecentVersion(1);
+
+        ArrayList<String> deploymentArtifactList = new ArrayList<>();
+        deploymentArtifactList.add("id-456");
+        deploymentArtifactRef.setDeploymentArtifacts(deploymentArtifactList);
+
+        msInstance.setDeploymentArtifactsInfo(deploymentArtifactRef);
+
+        return msInstance;
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/SpecificationObjectMother.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/objectmothers/SpecificationObjectMother.java
new file mode 100644 (file)
index 0000000..3390998
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.objectmothers;
+
+import org.onap.dcaegen2.platform.mod.model.specification.DeploymentType;
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.util.TestUtil;
+
+public class SpecificationObjectMother {
+    public static final String SPEC_REQUEST = "src/test/resources/http/requests/CreateSpecificationRequest.json";
+    public static final String SPEC_RESPONSE = "src/test/resources/http/requests/CreateSpecificationResponse.json";
+
+    public static SpecificationRequest getSpecificationRequest() {
+        return TestUtil.deserializeJsonFileToModel(SPEC_REQUEST, SpecificationRequest.class);
+    }
+
+    public static Specification getMockSpecification(DeploymentType type) {
+        Specification specification = TestUtil.deserializeJsonFileToModel(SPEC_RESPONSE, Specification.class);
+        specification.setType(type);
+        return specification;
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/persistence/DeploymentArtifactGatewayTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/persistence/DeploymentArtifactGatewayTest.java
new file mode 100644 (file)
index 0000000..9bcd46c
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.persistence;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactFilter;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactSearch;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.mongo.deploymentartifact.DeploymentArtifactMongoGateway;
+import org.onap.dcaegen2.platform.mod.mongo.deploymentartifact.DeploymentArtifactMongoRepo;
+import org.onap.dcaegen2.platform.mod.objectmothers.DeploymentArtifactObjectMother;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactGateway;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Disabled("Embedded mongodb jar is not available in the maven repo.")
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class DeploymentArtifactGatewayTest {
+
+    DeploymentArtifactGateway gateway;
+
+    @Autowired
+    DeploymentArtifactMongoRepo repo;
+
+    @Autowired
+    MongoOperations operations;
+
+    @BeforeEach
+    public void setUp(){
+        gateway = new DeploymentArtifactMongoGateway(repo);
+
+        operations.dropCollection(DeploymentArtifact.class);
+
+        String r_2008 = "2008";
+        String r_2010 = "2010";
+
+        DeploymentArtifactStatus inDev = DeploymentArtifactStatus.IN_DEV;
+        DeploymentArtifactStatus devComplete = DeploymentArtifactStatus.DEV_COMPLETE;
+
+        String tag_1 = "hello-one";
+        String tag_2 = "hello-two";
+        String tag_3 = "hello-three";
+
+        DeploymentArtifact artifact_1 = getDeploymentArtifact(r_2008, inDev, tag_1);
+        DeploymentArtifact artifact_2 = getDeploymentArtifact(r_2010, devComplete, tag_2);
+        DeploymentArtifact artifact_3 = getDeploymentArtifact(r_2008, devComplete, tag_3);
+
+        operations.insertAll(Arrays.asList(artifact_1, artifact_2, artifact_3));
+        operations.findAll(DeploymentArtifact.class).forEach(System.out::println);
+
+        System.out.println();
+    }
+
+    private static DeploymentArtifact getDeploymentArtifact(String r_2008, DeploymentArtifactStatus inDev,
+                                                            String tag) {
+        DeploymentArtifact artifact_1 = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(inDev);
+        artifact_1.getMsInstanceInfo().setRelease(r_2008);
+        //Currently searching tag in filename as it is not present in DeploymentArtifact record
+        artifact_1.setFileName(tag);
+        artifact_1.setId(null);
+
+        return artifact_1;
+    }
+
+    @Test
+    public void findByOnlyRelease() throws Exception {
+        DeploymentArtifactSearch search = new DeploymentArtifactSearch();
+        DeploymentArtifactFilter filter = new DeploymentArtifactFilter();
+        filter.setRelease("2008");
+        search.setFilter(filter);
+
+        List<DeploymentArtifact> artifacts = gateway.findAll(search);
+        Assertions.assertThat(artifacts.size()).isEqualTo(2);
+    }
+
+    @Test
+    public void findWithOnlyStatus() throws Exception {
+        DeploymentArtifactSearch search = new DeploymentArtifactSearch();
+        DeploymentArtifactFilter filter = new DeploymentArtifactFilter();
+        filter.setStatus(DeploymentArtifactStatus.IN_DEV);
+        search.setFilter(filter);
+
+        List<DeploymentArtifact> artifacts = gateway.findAll(search);
+
+        Assertions.assertThat(artifacts.size()).isEqualTo(1);
+    }
+
+    @Test
+    public void findWithStatusAndRelease() throws Exception {
+        DeploymentArtifactSearch search = new DeploymentArtifactSearch();
+        DeploymentArtifactFilter filter = new DeploymentArtifactFilter();
+        filter.setStatus(DeploymentArtifactStatus.DEV_COMPLETE);
+        filter.setRelease("2008");
+        search.setFilter(filter);
+
+        List<DeploymentArtifact> artifacts = gateway.findAll(search);
+
+        Assertions.assertThat(artifacts.size()).isEqualTo(3);
+    }
+
+    @Test
+    public void findWithTag() throws Exception {
+        DeploymentArtifactSearch search = new DeploymentArtifactSearch();
+        DeploymentArtifactFilter filter = new DeploymentArtifactFilter();
+        filter.setTag("hello-one");
+        search.setFilter(filter);
+
+        List<DeploymentArtifact> artifacts = gateway.findAll(search);
+
+        Assertions.assertThat(artifacts.size()).isEqualTo(1);
+    }
+
+    @Test
+    public void findWithNoQuery() throws Exception {
+        List<DeploymentArtifact> artifacts = gateway.findAll(new DeploymentArtifactSearch());
+        Assertions.assertThat(artifacts.size()).isEqualTo(0);
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/util/TestUtil.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/util/TestUtil.java
new file mode 100644 (file)
index 0000000..f9a45da
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * ============LICENSE_START======================================================= 
+ *  org.onap.dcae 
+ *  ================================================================================ 
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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========================================================= 
+ */
+
+package org.onap.dcaegen2.platform.mod.util;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+public class TestUtil {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private TestUtil() {}
+
+    public static Map<String, Object> readJsonFileAsObjectMap(String filePath) {
+        try {
+            return MAPPER.readValue(new File(filePath), new TypeReference<Map<String, Object>>() {});
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+
+    public static <T> T deserializeJsonFileToModel(String filePath, Class<T> modelClass) {
+        try {
+            return MAPPER.readValue(new File(filePath), modelClass);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/BaseMicroserviceControllerTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/BaseMicroserviceControllerTest.java
new file mode 100644 (file)
index 0000000..c89b4dc
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.exceptions.OperationNotAllowedException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.ResourceConflictException;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother;
+import org.onap.dcaegen2.platform.mod.web.controller.BaseMicroserviceController;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.MsService;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import java.util.Arrays;
+
+import static org.onap.dcaegen2.platform.mod.model.exceptions.ErrorMessages.MICROSERVICE_NAME_CONFLICT_MESSAGE;
+
+@ExtendWith(SpringExtension.class)
+@WebMvcTest(BaseMicroserviceController.class)
+class BaseMicroserviceControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    private MsService mockBaseMsService;
+
+    @BeforeEach
+    void setUp() {
+    }
+
+    @Test
+    void test_GetAllBaseMicroservices_returnsListOfDTOs() throws Exception {
+        //arrange
+        BaseMicroservice ms1 = new BaseMicroservice();
+        ms1.setName("HelloWorld1");
+        BaseMicroservice ms2 = new BaseMicroservice();
+        ms2.setName("HelloWorld2");
+
+        Mockito.when(mockBaseMsService.getAllMicroservices()).thenReturn(Arrays.asList(ms1, ms2));
+
+        //act/assert
+        mockMvc.perform(MockMvcRequestBuilders.get("/api/base-microservice")
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)));
+    }
+
+    @Test
+    void test_addBaseMicroservice_returnsMicroservice() throws Exception {
+        //arrange
+        MicroserviceCreateRequest microserviceRequest = BaseMsObjectMother.createMockMsRequest();
+
+        //response
+        BaseMicroservice microserviceDao = BaseMsObjectMother.createMockMsObject();
+
+        Mockito.when(mockBaseMsService.createMicroservice(microserviceRequest)).thenReturn(microserviceDao);
+
+        //act/assert
+        mockMvc.perform(MockMvcRequestBuilders.post("/api/base-microservice")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(microserviceRequest))
+                .characterEncoding("utf-8"))
+                .andExpect(MockMvcResultMatchers.status().isCreated())
+                .andExpect((MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(BaseMsObjectMother.BASE_MS_ID))))
+                .andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo(BaseMsObjectMother.BASE_MS_NAME)))
+                .andExpect(MockMvcResultMatchers.jsonPath("$.metadata.createdBy", Matchers.equalTo(BaseMsObjectMother.USER)));
+    }
+
+    @Test
+    void test_addBaseMicroserviceWithDuplicateName_shouldThrowConflictError() throws Exception{
+        //arrange
+        MicroserviceCreateRequest microserviceRequest = BaseMsObjectMother.createMockMsRequest();
+        Mockito.when(mockBaseMsService.createMicroservice(ArgumentMatchers.any())).thenThrow(new ResourceConflictException(MICROSERVICE_NAME_CONFLICT_MESSAGE));
+
+        //act/assert
+        mockMvc.perform(MockMvcRequestBuilders.post("/api/base-microservice")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(microserviceRequest)))
+                .andExpect(MockMvcResultMatchers.status().isConflict());
+    }
+
+    @Test
+    void test_updateBaseMicroserviceEndpoint() throws Exception{
+        MicroserviceUpdateRequest microserviceRequest = BaseMsObjectMother.createUpdateMsRequest();
+        String requestedMsId = "id-123";
+
+        mockMvc.perform(MockMvcRequestBuilders.patch(String.format(BaseMicroserviceController.API_BASE_MICROSERVICE + "/%s", requestedMsId))
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(microserviceRequest))
+                .characterEncoding("utf-8"))
+                .andExpect(MockMvcResultMatchers.status().isNoContent());
+        Mockito.verify(mockBaseMsService, Mockito.times(1)).updateMicroservice(requestedMsId, microserviceRequest);
+    }
+
+    @Test
+    void test_OperationNotAllowedExceptionThrows409() throws Exception{
+        MicroserviceUpdateRequest microserviceRequest = BaseMsObjectMother.createUpdateMsRequest();
+        String requestedMsId = "id-123";
+        Mockito.doThrow(new OperationNotAllowedException("")).
+                when(mockBaseMsService).updateMicroservice(requestedMsId, microserviceRequest);
+
+        mockMvc.perform(MockMvcRequestBuilders.patch(String.format(BaseMicroserviceController.API_BASE_MICROSERVICE + "/%s", requestedMsId))
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(microserviceRequest)))
+                .andExpect(MockMvcResultMatchers.status().isConflict());
+    }
+
+    @Test
+    void test_validateMsRequestShouldThrowCorrectResponse() throws Exception {
+        //arrange
+        MicroserviceCreateRequest microserviceRequest = BaseMsObjectMother.createMockMsRequest();
+        microserviceRequest.setName(" ");
+        microserviceRequest.setTag("123");
+        microserviceRequest.setServiceName("123");
+        microserviceRequest.setUser(" ");
+
+        //response
+        BaseMicroservice microserviceDao = BaseMsObjectMother.createMockMsObject();
+
+        Mockito.when(mockBaseMsService.createMicroservice(microserviceRequest)).thenReturn(microserviceDao);
+
+        //act/assert
+        mockMvc.perform(MockMvcRequestBuilders.post("/api/base-microservice")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(microserviceRequest))
+                .characterEncoding("utf-8"))
+                .andExpect(MockMvcResultMatchers.status().isBadRequest())
+                .andExpect(MockMvcResultMatchers.jsonPath("$.message", Matchers.equalTo("Validation failed.")))
+                .andExpect(MockMvcResultMatchers.jsonPath("$.errors", Matchers.hasSize(4)))
+                ;
+    }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/DeploymentArtifactControllerTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/DeploymentArtifactControllerTest.java
new file mode 100644 (file)
index 0000000..c8942b1
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.BlueprintFileNameCreateException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.DeploymentArtifactNotFound;
+import org.onap.dcaegen2.platform.mod.model.restapi.DeploymentArtifactPatchRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother;
+import org.onap.dcaegen2.platform.mod.objectmothers.DeploymentArtifactObjectMother;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.onap.dcaegen2.platform.mod.web.controller.DeploymentArtifactController;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactService;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+
+@WebMvcTest(DeploymentArtifactController.class)
+class DeploymentArtifactControllerTest {
+
+    @Autowired
+    MockMvc mockMvc;
+
+    @MockBean
+    DeploymentArtifactService service;
+
+    @BeforeEach
+    void setUp() {
+    }
+
+    @Test
+    void test_GenerateDeploymentArtifactEndpoint_returnsBlueprint() throws Exception{
+        String url = String.format("/api/deployment-artifact/%s?user=%s", MsInstanceObjectMother.MS_INSTANCE_ID, MsInstanceObjectMother.USER);
+        DeploymentArtifact response = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+
+        Mockito.when(service.generateDeploymentArtifact(MsInstanceObjectMother.MS_INSTANCE_ID, MsInstanceObjectMother.USER)).thenReturn(response);
+
+        mockMvc.perform(post(url))
+                    .andExpect(MockMvcResultMatchers.status().isCreated())
+                    .andExpect(MockMvcResultMatchers.jsonPath("$.fileName").exists())
+                    .andExpect(MockMvcResultMatchers.jsonPath("$.content").exists());
+
+        Mockito.verify(service, Mockito.times(1)).generateDeploymentArtifact(MsInstanceObjectMother.MS_INSTANCE_ID, MsInstanceObjectMother.USER);
+    }
+
+    @Test
+    void test_RaiseExceptionIfBlueprintNameCanNotBeCreated() throws Exception{
+        String url = String.format("/api/deployment-artifact/%s?user=%s", MsInstanceObjectMother.MS_INSTANCE_ID,
+                MsInstanceObjectMother.USER);
+        DeploymentArtifact response = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+
+        Mockito.when(service.generateDeploymentArtifact(MsInstanceObjectMother.MS_INSTANCE_ID, MsInstanceObjectMother.USER)).thenThrow(new BlueprintFileNameCreateException(""));
+
+        mockMvc.perform(post(url))
+                .andExpect(MockMvcResultMatchers.status().is4xxClientError());
+    }
+
+    @Test
+    void test_GetAllDeploymentArtifactsShouldReturnList() throws Exception{
+        List<DeploymentArtifact> daos = createDaos();
+        Mockito.when(service.getAllDeploymentArtifacts()).thenReturn(daos);
+
+        mockMvc.perform(get("/api/deployment-artifact"))
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)));
+
+    }
+
+    private List<DeploymentArtifact> createDaos() {
+        DeploymentArtifact dao1 = new DeploymentArtifact();
+        dao1.setId("123");
+        DeploymentArtifact dao2 = new DeploymentArtifact();
+        dao2.setId("456");
+
+        return Arrays.asList(dao1, dao2);
+    }
+
+    @Test
+    void test_GetAllDeploymentArtifactTestShouldReturnAList() throws Exception{
+
+        mockMvc.perform(get(DeploymentArtifactController.DEPLOYMENT_ARTIFACTS_BASE_URL + DeploymentArtifactController.GET_STATUSES))
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(DeploymentArtifactStatus.values().length)));
+    }
+
+    @Test
+    void test_ifUserIsNullRaiseException() throws Exception{
+        String id = "id-123";
+        String user = "";
+
+        mockMvc.perform(patch(DeploymentArtifactController.DEPLOYMENT_ARTIFACTS_BASE_URL + "/" + id + "?user=" + user)
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(new DeploymentArtifactPatchRequest())))
+                .andExpect(MockMvcResultMatchers.status().isBadRequest());
+    }
+
+    @Test
+    void test_ChangeStatusOfDeploymentArtifact() throws Exception{
+
+        String id = "id-123";
+        String user = "user1";
+        DeploymentArtifactPatchRequest partialDto = new DeploymentArtifactPatchRequest();
+        partialDto.setStatus(DeploymentArtifactStatus.DEV_COMPLETE);
+
+        mockMvc.perform(patch(DeploymentArtifactController.DEPLOYMENT_ARTIFACTS_BASE_URL + "/" + id + "?user=" + user)
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(partialDto)))
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$.message").exists());
+
+        Mockito.verify(service, Mockito.times(1)).updateDeploymentArtifact(id, partialDto, user);
+
+    }
+
+    @Test
+    void test_deploymentArtifactIdNotFound() throws Exception{
+        String wrongId = "wrong-id";
+        DeploymentArtifactPatchRequest partialDto = new DeploymentArtifactPatchRequest();
+        partialDto.setStatus(DeploymentArtifactStatus.DEV_COMPLETE);
+
+        Mockito.doThrow(new DeploymentArtifactNotFound("")).when(service).
+                updateDeploymentArtifact(wrongId, partialDto, "user-1");
+
+        mockMvc.perform(patch(DeploymentArtifactController.DEPLOYMENT_ARTIFACTS_BASE_URL + "/" + wrongId + "?user=" + "user-1")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(BaseMsObjectMother.asJsonString(partialDto)))
+                .andExpect(MockMvcResultMatchers.status().isBadRequest());
+    }
+
+    @Test
+    void test_deleteDeploymentArtifactEndpoint() throws Exception{
+        String deploymentArtifactId = "id-123";
+        String user = "user-1";
+        mockMvc.perform(delete(DeploymentArtifactController.DEPLOYMENT_ARTIFACTS_BASE_URL + "/" + deploymentArtifactId + "?user=" + user))
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$.message").exists());
+        Mockito.verify(service, Mockito.times(1)).deleteDeploymentArtifact(deploymentArtifactId);
+    }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/MicroserviceInstanceControllerTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/MicroserviceInstanceControllerTest.java
new file mode 100644 (file)
index 0000000..852fb10
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.onap.dcaegen2.platform.mod.web.controller.MicroserviceInstanceController;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@WebMvcTest(MicroserviceInstanceController.class)
+class MicroserviceInstanceControllerTest {
+
+    @MockBean
+    MsInstanceService service;
+
+    @Autowired
+    MockMvc mockMvc;
+
+    @BeforeEach
+    void setUp() {
+    }
+
+    @Test
+    void getAll() throws Exception {
+        MsInstance instance_1 = MsInstance.builder().id("123").build();
+        MsInstance instance_2 = MsInstance.builder().id("345").build();
+
+        when(service.getAll()).thenReturn(Arrays.asList(instance_1,instance_2));
+
+        mockMvc.perform(get("/api/microservice-instance")
+                .contentType(MediaType.APPLICATION_JSON))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$",hasSize(2)));
+        verify(service, times(1)).getAll();
+    }
+
+    @Test
+    void createMsInstance_shouldReturn201AndResponseBody() throws Exception {
+
+        MsInstanceRequest request = getMsInstanceMockRequest();
+        MsInstance msInstance = createMsInstance();
+
+        when(service.createMicroserviceInstance(BaseMsObjectMother.BASE_MS_NAME, request)).thenReturn(msInstance);
+
+        mockMvc.perform(MockMvcRequestBuilders.post("/api/microservice-instance/"+ BaseMsObjectMother.BASE_MS_NAME)
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(BaseMsObjectMother.asJsonString(request)).accept(MediaType.APPLICATION_JSON))
+                            .andExpect(status().isCreated())
+                            .andExpect(jsonPath("$.name",equalTo(MS_INSTANCE_NAME)));
+
+        verify(service, times(1)).createMicroserviceInstance(BaseMsObjectMother.BASE_MS_NAME,request);
+    }
+
+    @Test
+    void patchMsInstance_shouldReturn204NoContent() throws Exception{
+        //given
+        String updatedVersion = "updatedVersion";
+        String updatedRelease = "updatedRelease";
+
+        MsInstance mockedMsInstance = prepareMockMsInstance(updatedVersion, updatedRelease);
+        String msInstanceId = mockedMsInstance.getId();
+
+        MsInstanceUpdateRequest updateRequest = prepareMsInstanceUpdateRequest(updatedVersion, updatedRelease);
+
+       when(service.updateMsInstance(updateRequest, msInstanceId)).thenReturn(mockedMsInstance);
+
+       mockMvc.perform(patch("/api/microservice-instance/" + msInstanceId)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(BaseMsObjectMother.asJsonString(updateRequest)))
+                        .andExpect(status().isOk())
+                        .andExpect(jsonPath("$.release", equalTo(updatedRelease)))
+                        .andExpect(jsonPath("$.version", equalTo(updatedVersion)))
+                        .andExpect(jsonPath("$.metadata.scrumLead", equalTo("updatedScrumLead")));
+
+       verify(service, times(1)).updateMsInstance(updateRequest, msInstanceId);
+    }
+
+    private MsInstanceUpdateRequest prepareMsInstanceUpdateRequest(String updatedVersion, String updatedRelease) {
+        MsInstanceUpdateRequest updateRequest = new MsInstanceUpdateRequest();
+        updateRequest.setRelease(updatedRelease);
+        updateRequest.setVersion(updatedVersion);
+        updateRequest.setMetadata(prepareMetadataToBeUpdated());
+        return updateRequest;
+    }
+
+    private Map<String, Object> prepareMetadataToBeUpdated() {
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("scrumLead", "updatedScrumLead");
+        return metadata;
+    }
+
+    private MsInstance prepareMockMsInstance(String updatedVersion, String updatedRelease) {
+        MsInstance msInstanceToBeUpdated = MsInstanceObjectMother.createMsInstance();
+        msInstanceToBeUpdated.setVersion(updatedVersion);
+        msInstanceToBeUpdated.setRelease(updatedRelease);
+        msInstanceToBeUpdated.getMetadata().put("scrumLead", "updatedScrumLead");
+        return msInstanceToBeUpdated;
+    }
+
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/MsRequestValidationTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/MsRequestValidationTest.java
new file mode 100644 (file)
index 0000000..d71f8dd
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import java.util.Set;
+
+public class MsRequestValidationTest {
+
+    public Validator validator;
+    private MicroserviceCreateRequest request;
+
+    @BeforeEach
+    public void setup(){
+        validator = Validation.buildDefaultValidatorFactory().getValidator();
+        request = BaseMsObjectMother.createMockMsRequest();
+    }
+
+    @Test
+    void test_msNameShouldNotBeBlank(){
+        request.setName("  ");
+        Set<ConstraintViolation<MicroserviceCreateRequest>> violations = validator.validate(request);
+        Assertions.assertThat(violations.size()).isEqualTo(1);
+    }
+
+    @Test
+    void test_msTagShouldNotBeNull(){
+        request.setTag(null);
+        Set<ConstraintViolation<MicroserviceCreateRequest>> violations = validator.validate(request);
+        Assertions.assertThat(violations.size()).isEqualTo(1);
+
+    }
+
+    @Test
+    void test_msTagShouldFollowRegex() throws Exception{
+        request.setTag("ms-1");
+        Set<ConstraintViolation<MicroserviceCreateRequest>> violations = validator.validate(request);
+        Assertions.assertThat(violations.size()).isEqualTo(1);
+    }
+
+    @Test
+    void test_msTagSizeValidation() throws Exception {
+        request.setTag("core-name-should-not-exceed-fifty-chars-core-name-should-not-exceed-fifty-chars");
+        Set<ConstraintViolation<MicroserviceCreateRequest>> violations = validator.validate(request);
+        Assertions.assertThat(violations.size()).isEqualTo(1);
+    }
+
+    @Test
+    void test_msServiceNameShouldFollowRegex() throws Exception{
+        request.setServiceName("ms-1");
+        Set<ConstraintViolation<MicroserviceCreateRequest>> violations = validator.validate(request);
+        Assertions.assertThat(violations.size()).isEqualTo(1);
+    }
+
+    @Test
+    void test_userShouldNotBeBlank(){
+        request.setUser("  ");
+        Set<ConstraintViolation<MicroserviceCreateRequest>> violations = validator.validate(request);
+        Assertions.assertThat(violations.size()).isEqualTo(1);
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/SpecificationControllerTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/SpecificationControllerTest.java
new file mode 100644 (file)
index 0000000..64f9c00
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web;
+
+import org.onap.dcaegen2.platform.mod.model.specification.DeploymentType;
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.web.controller.SpecificationController;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother.asJsonString;
+import static org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother.MS_INSTANCE_ID;
+import static org.onap.dcaegen2.platform.mod.objectmothers.SpecificationObjectMother.getMockSpecification;
+import static org.onap.dcaegen2.platform.mod.objectmothers.SpecificationObjectMother.getSpecificationRequest;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@WebMvcTest(SpecificationController.class)
+public class SpecificationControllerTest {
+
+    @Autowired
+    MockMvc mockMvc;
+
+    @MockBean
+    private SpecificationService mockSpecificationService;
+
+    @BeforeEach
+    void setup() {
+    }
+
+    @Test
+    void test_addSpecification_returnsSpecification() throws Exception {
+        //arrange
+        SpecificationRequest specificationRequest = getSpecificationRequest();
+        Specification specification = getMockSpecification(DeploymentType.DOCKER);
+
+        when(mockSpecificationService.createSpecification(MS_INSTANCE_ID, specificationRequest)).thenReturn(specification);
+
+        //act/assert
+        mockMvc.perform(post("/api/specification/" + MS_INSTANCE_ID)
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(asJsonString(specificationRequest)).accept(MediaType.APPLICATION_JSON))
+                .andExpect(jsonPath("$.id", notNullValue()))
+                .andExpect(status().isCreated());
+        verify(mockSpecificationService, times(1)).createSpecification(MS_INSTANCE_ID, specificationRequest);
+    }
+
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsInstanceServiceImplTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsInstanceServiceImplTest.java
new file mode 100644 (file)
index 0000000..1d84b60
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.MsInstanceInfo;
+import org.onap.dcaegen2.platform.mod.model.exceptions.msinstance.MsInstanceNotFoundException;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.MsInstanceRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.MsService;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactService;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceGateway;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceServiceImpl;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceStatusChangeHandler;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class MsInstanceServiceImplTest {
+
+    @Spy
+    private MsInstanceServiceImpl service = new MsInstanceServiceImpl();
+
+    @Mock
+    private MsInstanceGateway msInstanceRepository;
+
+    @Mock
+    private MsService msService;
+
+    @Mock
+    private SpecificationService specificationService;
+
+    @Mock
+    private DeploymentArtifactService deploymentArtifactService;
+
+    @Mock
+    private MsInstanceStatusChangeHandler msInstanceStatusChangeHandler;
+
+
+    @BeforeEach
+    void setUp() {
+        service.setMsService(msService);
+        service.setSpecificationService(specificationService);
+        service.setDeploymentArtifactService(deploymentArtifactService);
+        service.setMsInstanceRepository(msInstanceRepository);
+        service.setMsInstanceStatusChangeHandler(msInstanceStatusChangeHandler);
+    }
+
+    @Test
+    void getAll() {
+        MsInstance instance_1 = MsInstance.builder().id("123").build();
+        MsInstance instance_2 = MsInstance.builder().id("345").build();
+
+        when(msInstanceRepository.findAll()).thenReturn(Arrays.asList(instance_1, instance_2));
+
+        List<MsInstance> instances = service.getAll();
+
+        assertThat(instances.size()).isEqualTo(2);
+        verify(msInstanceRepository, times(1)).findAll();
+    }
+
+    @Test
+    void test_getMsInstanceById() throws Exception{
+        MsInstance expected = MsInstanceObjectMother.createMsInstance();
+
+        when(msInstanceRepository.findById(MS_INSTANCE_ID)).thenReturn(Optional.of(expected));
+
+        MsInstance original = service.getMsInstanceById(MS_INSTANCE_ID);
+
+        assertThat(original.getId()).isEqualTo(expected.getId());
+    }
+
+    @Test
+    void test_msIntanceNotFound_willRaiseException() throws Exception{
+        when(msInstanceRepository.findById(MS_INSTANCE_ID)).thenReturn(Optional.empty());
+        assertThatExceptionOfType(MsInstanceNotFoundException.class).isThrownBy(
+                () -> service.getMsInstanceById(MS_INSTANCE_ID));
+    }
+
+    //TODO require cleaning and more assertions
+    @Test
+    void createMicroserviceInstance() {
+
+        BaseMicroservice microservice = BaseMsObjectMother.createMockMsObject();
+        MsInstanceRequest request = getMsInstanceMockRequest();
+        MsInstance msInstanceMockDao = createMsInstance();
+
+        when(msService.getMicroserviceByName(BaseMsObjectMother.BASE_MS_NAME)).thenReturn(microservice);
+        when(msInstanceRepository.findByNameAndRelease(request.getName(), request.getRelease()))
+                .thenReturn(Optional.empty());
+        when(msInstanceRepository.save(any())).thenReturn(msInstanceMockDao);
+
+        MsInstance msInstance = service.createMicroserviceInstance(BaseMsObjectMother.BASE_MS_NAME,request);
+
+        assertThat(msInstance.getId()).isEqualTo(msInstance.getId());
+        assertThat(msInstance.getName()).isEqualTo(msInstance.getName());
+        assertThat(msInstance.getMsInfo().keySet()).isEqualTo(msInstanceMockDao.getMsInfo().keySet());
+
+        verify(msService, times(1)).getMicroserviceByName(BaseMsObjectMother.BASE_MS_NAME);
+        verify(msInstanceRepository, times(1)).save(any(MsInstance.class));
+        verify(msService, times(1)).
+                saveMsInstanceReferenceToMs(microservice, msInstance);
+
+    }
+
+    @Test
+    void test_updateMsInstance() {
+
+    }
+
+    @Test
+    void updateStatusBasedOnDeploymentArtifactsStatuses() {
+        MsInstance msInstance = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+        when(msInstanceRepository.findById(msInstance.getId())).thenReturn(Optional.of(msInstance));
+
+        service.updateStatusBasedOnDeploymentArtifactsStatuses(msInstance.getId());
+
+        verify(msInstanceStatusChangeHandler, times(1)).updateStatusBasedOnDeploymentArtifactsStatuses(msInstance);
+        verify(service, times(1)).updateMsInstance(msInstance);
+
+    }
+
+    @Test
+    void test_removeDeploymentArtifactFromMsInstance() {
+        MsInstance msInstance = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+        DeploymentArtifact deploymentArtifact = createDeploymentArtifact(msInstance);
+
+        when(msInstanceRepository.findById(msInstance.getId())).thenReturn(Optional.of(msInstance));
+        //when(msInstanceStatusChangeHandler.updateStatusBasedOnDeploymentArtifactsStatuses(any())).thenReturn(msInstance);
+
+        service.removeDeploymentArtifactFromMsInstance(deploymentArtifact);
+
+        assertThat(msInstance.getDeploymentArtifactsInfo().getDeploymentArtifacts().contains(deploymentArtifact.getId())).isFalse();
+        verify(msInstanceStatusChangeHandler, times(1)).updateStatusBasedOnDeploymentArtifactsStatuses(msInstance);
+        verify(service, times(1)).updateMsInstance(msInstance);
+
+    }
+
+    @Test
+    void updateMicroserviceReference() throws Exception{
+        BaseMicroservice microservice = BaseMsObjectMother.createMockMsObject();
+        MsInstance msInstance_1 = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+        msInstance_1.setId("instance-1");
+        msInstance_1.getMsInfo().put("name", "old-ms");
+        MsInstance msInstance_2 = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+        msInstance_2.setId("instance-2");
+        msInstance_2.getMsInfo().put("name", "old-ms");
+
+        when(msInstanceRepository.findById("instance-1")).thenReturn(Optional.of(msInstance_1));
+        when(msInstanceRepository.findById("instance-2")).thenReturn(Optional.of(msInstance_2));
+
+        service.updateMicroserviceReference(microservice);
+
+        assertThat(msInstance_1.getName()).isEqualTo(microservice.getName());
+        assertThat(msInstance_2.getName()).isEqualTo(microservice.getName());
+
+        assertThat(msInstance_1.getMsInfo().get("name")).isEqualTo(microservice.getName());
+        assertThat(msInstance_2.getMsInfo().get("name")).isEqualTo(microservice.getName());
+
+        verify(service, times(2)).getMsInstanceById(anyString());
+        verify(msInstanceRepository, times(2)).save(any(MsInstance.class));
+    }
+
+    private DeploymentArtifact createDeploymentArtifact(MsInstance msInstance) {
+        DeploymentArtifact deploymentArtifact = new DeploymentArtifact();
+        deploymentArtifact.setId(msInstance.getDeploymentArtifactsInfo().getDeploymentArtifacts().get(0));
+
+        MsInstanceInfo msInstanceInfo = new MsInstanceInfo();
+        msInstanceInfo.setId(msInstance.getId());
+        deploymentArtifact.setMsInstanceInfo(msInstanceInfo);
+        return deploymentArtifact;
+    }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsInstanceStatusChangeHandlerTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsInstanceStatusChangeHandlerTest.java
new file mode 100644 (file)
index 0000000..9339b26
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service;
+
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstanceStatus;
+import org.onap.dcaegen2.platform.mod.objectmothers.DeploymentArtifactObjectMother;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.onap.dcaegen2.platform.mod.web.service.deploymentartifact.DeploymentArtifactService;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceStatusChangeHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class MsInstanceStatusChangeHandlerTest {
+
+    MsInstanceStatusChangeHandler statusChangeHandler;
+
+    @Mock
+    MsInstanceService msInstanceService;
+
+    @Mock
+    DeploymentArtifactService deploymentArtifactService;
+
+    @BeforeEach
+    void setup() throws Exception{
+        statusChangeHandler = new MsInstanceStatusChangeHandler();
+        statusChangeHandler.setMsInstanceService(msInstanceService);
+        statusChangeHandler.setDeploymentArtifactService(deploymentArtifactService);
+    }
+
+    @Test
+    void handleStatusChangeFromDeploymentArtifactsWithDevComplete() {
+        //arrange
+        MsInstance msInstance = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+
+        when(deploymentArtifactService.findByMsInstanceId(msInstance.getId())).thenReturn(
+                DeploymentArtifactObjectMother.createMockDeploymentArtifactsWithDifferentStatuses(true)
+        );
+
+        //act
+        statusChangeHandler.updateStatusBasedOnDeploymentArtifactsStatuses(msInstance);
+
+        //assert
+        assertThat(msInstance.getStatus()).isEqualTo(MsInstanceStatus.DEV_COMPLETE);
+        verify(deploymentArtifactService, times(1)).findByMsInstanceId(msInstance.getId());
+    }
+
+    @Test
+    void handleStatusChangeFromDeploymentArtifactsWithoutDevComplete() {
+        //arrange
+        MsInstance msInstance = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+
+        //when(msInstanceService.getMsInstanceById(msInstance.getId())).thenReturn(msInstance);
+        when(deploymentArtifactService.findByMsInstanceId(msInstance.getId())).thenReturn(
+                DeploymentArtifactObjectMother.createMockDeploymentArtifactsWithDifferentStatuses(false)
+        );
+
+        //act
+        statusChangeHandler.updateStatusBasedOnDeploymentArtifactsStatuses(msInstance);
+
+        //assert
+        assertThat(msInstance.getStatus()).isEqualTo(MsInstanceStatus.IN_DEV);
+        verify(deploymentArtifactService, times(1)).findByMsInstanceId(msInstance.getId());
+    }
+
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsServiceImplTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/MsServiceImplTest.java
new file mode 100644 (file)
index 0000000..8604f1a
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service;
+
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMicroservice;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsLocation;
+import org.onap.dcaegen2.platform.mod.model.basemicroservice.BaseMsType;
+import org.onap.dcaegen2.platform.mod.model.exceptions.ResourceConflictException;
+import org.onap.dcaegen2.platform.mod.model.exceptions.basemicroservice.BaseMicroserviceNotFoundException;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceCreateRequest;
+import org.onap.dcaegen2.platform.mod.model.restapi.MicroserviceUpdateRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.BaseMicroserviceGateway;
+import org.onap.dcaegen2.platform.mod.web.service.basemicroservice.MsServiceImpl;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.*;
+
+import static org.onap.dcaegen2.platform.mod.model.exceptions.ErrorMessages.MICROSERVICE_NAME_CONFLICT_MESSAGE;
+import static org.onap.dcaegen2.platform.mod.model.exceptions.ErrorMessages.MICROSERVICE_TAG_CONFLICT_MESSAGE;
+import static org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother.createMockMsObject;
+import static org.onap.dcaegen2.platform.mod.objectmothers.BaseMsObjectMother.createMockMsRequest;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class MsServiceImplTest {
+
+    @Mock
+    private BaseMicroserviceGateway repository;
+
+    @Mock
+    private MsInstanceService msInstanceService;
+
+    @Spy
+    private MsServiceImpl baseMsService = new MsServiceImpl();
+
+    @BeforeEach
+    void setup() throws Exception{
+        baseMsService.setRepository(repository);
+        baseMsService.setMsInstanceService(msInstanceService);
+    }
+
+    /**GET MICROSERVICE TESTS*/
+    @Test
+    void getAll() {
+        //arrange
+        BaseMicroservice ms1 = new BaseMicroservice();
+        ms1.setName("HelloWorld1");
+        BaseMicroservice ms2 = new BaseMicroservice();
+        ms2.setName("HelloWorld2");
+
+        when(repository.findAll()).thenReturn(Arrays.asList(ms1, ms2));
+
+        //act
+        List<BaseMicroservice> microservices = baseMsService.getAllMicroservices();
+
+        //assert
+        assertThat(microservices).hasSizeGreaterThan(0);
+    }
+
+    @Test
+    void test_getMicroserviceById() throws Exception{
+        BaseMicroservice expectedMicroservice = BaseMsObjectMother.createMockMsObject();
+        String baseMsId = BaseMsObjectMother.BASE_MS_ID;
+
+        when(repository.findById(baseMsId)).thenReturn(Optional.of(expectedMicroservice));
+
+        BaseMicroservice resultMicroservice = baseMsService.getMicroserviceById(baseMsId);
+
+        assertThat(resultMicroservice).isEqualTo(expectedMicroservice);
+        verify(repository, times(1)).findById(baseMsId);
+    }
+
+    @Test
+    void test_ifMicroserviceNotFoundRaiseException() throws Exception{
+        BaseMicroservice expectedMicroservice = BaseMsObjectMother.createMockMsObject();
+        String baseMsId = BaseMsObjectMother.BASE_MS_ID;
+
+        when(repository.findById(baseMsId)).thenReturn(Optional.empty());
+
+        assertThatExceptionOfType(BaseMicroserviceNotFoundException.class).isThrownBy(
+                () -> baseMsService.getMicroserviceById(baseMsId)
+        );
+    }
+
+    /**CREATE MICROSERVICE TESTS*/
+    @Test
+    void createMicroservice() {
+        //arrange
+        MicroserviceCreateRequest microserviceRequest = createMockMsRequest();
+        BaseMicroservice expected = createMockMsObject();
+
+        when(repository.save(any())).thenReturn(expected);
+
+        //act
+        BaseMicroservice actual = baseMsService.createMicroservice(microserviceRequest);
+
+        //assert
+        assertThat(actual.getMetadata().getCreatedBy()).isEqualTo(microserviceRequest.getUser());
+        assertThat(actual.getMetadata().getUpdatedBy()).isEqualTo(microserviceRequest.getUser());
+    }
+
+    @Test
+    void AddingMsWithDuplicateName_shouldThrowException() throws Exception{
+        //arrange
+        MicroserviceCreateRequest microserviceRequest = createMockMsRequest();
+        BaseMicroservice existedMicroservice = createMockMsObject();
+
+        when(repository.findByName(any())).thenReturn(Optional.of(existedMicroservice));
+
+        //act/assert
+        assertThatThrownBy(() -> baseMsService.createMicroservice((microserviceRequest)))
+                .isInstanceOf(ResourceConflictException.class)
+                .hasMessage(MICROSERVICE_NAME_CONFLICT_MESSAGE);
+    }
+    @Test
+    void AddingMsWithDuplicateTag_shouldThrowException() throws Exception{
+        //arrange
+        MicroserviceCreateRequest microserviceRequest = createMockMsRequest();
+        BaseMicroservice existedMicroservice = createMockMsObject();
+
+        when(repository.findByTag(any())).thenReturn(Optional.of(existedMicroservice));
+
+        //act/assert
+        assertThatThrownBy(() -> baseMsService.createMicroservice((microserviceRequest)))
+                .isInstanceOf(ResourceConflictException.class)
+                .hasMessage(MICROSERVICE_TAG_CONFLICT_MESSAGE);
+    }
+
+    /**UPDATE MICROSERVICE TESTS*/
+    @Test
+    void test_updateMicroservice() throws Exception{
+        MicroserviceUpdateRequest updateRequest = createUpdateMsRequest();
+
+        BaseMicroservice msToBeUpdated = BaseMsObjectMother.createMockMsObject();
+        Date updateTimeBefore = new Date(msToBeUpdated.getMetadata().getUpdatedOn().getTime());
+
+        String baseMsId = BaseMsObjectMother.BASE_MS_ID;
+
+        when(repository.findById(baseMsId)).thenReturn(Optional.of(msToBeUpdated));
+
+        baseMsService.updateMicroservice(baseMsId, updateRequest);
+
+        //assert
+        assertUpdatedMsFileds(updateRequest, msToBeUpdated, updateTimeBefore);
+        verify(baseMsService, times(1)).getMicroserviceById(baseMsId);
+        verify(msInstanceService, times(1)).updateMicroserviceReference(msToBeUpdated);
+        verify(repository, times(1)).save(msToBeUpdated);
+    }
+
+/*    @Test
+    void test_msTagChangeShouldNotBeAllowed() throws Exception{
+        MicroserviceCreateRequest updateRequest = new MicroserviceCreateRequest();
+        updateRequest.setTag("updateTag");
+        String baseMsId = BaseMsObjectMother.BASE_MS_ID;
+
+        assertThatExceptionOfType(OperationNotAllowedException.class).isThrownBy(
+                () -> baseMsService.updateMicroservice(baseMsId, updateRequest)
+        );
+    }*/
+
+    private void assertUpdatedMsFileds(MicroserviceUpdateRequest updateRequest, BaseMicroservice msToBeUpdated,
+                                       Date updateTimeBefore) {
+        assertThat(msToBeUpdated.getName()).isEqualTo(updateRequest.getName());
+        assertThat(msToBeUpdated.getLocation()).isEqualTo(updateRequest.getLocation());
+        assertThat(msToBeUpdated.getServiceName()).isEqualTo(updateRequest.getServiceName());
+        assertThat(msToBeUpdated.getNamespace()).isEqualTo(updateRequest.getNamespace());
+        assertThat(msToBeUpdated.getType()).isEqualTo(updateRequest.getType());
+
+        assertThat(msToBeUpdated.getMetadata().getUpdatedBy()).isEqualTo(updateRequest.getUser());
+        assertThat(msToBeUpdated.getMetadata().getUpdatedOn()).isNotEqualTo(updateTimeBefore);
+
+        assertThat(msToBeUpdated.getMetadata().getNotes()).isEqualTo(updateRequest.getMetadata().get("notes"));
+        assertThat(msToBeUpdated.getMetadata().getLabels()).isEqualTo(updateRequest.getMetadata().get("labels"));
+    }
+
+    private MicroserviceUpdateRequest createUpdateMsRequest() {
+        MicroserviceUpdateRequest updateRequest = new MicroserviceUpdateRequest();
+        updateRequest.setName("updatedName");
+        updateRequest.setLocation(BaseMsLocation.EDGE);
+        updateRequest.setServiceName("updatedServiceName");
+        updateRequest.setNamespace("updatedNameSpace");
+        updateRequest.setType(BaseMsType.ANALYTIC);
+        updateRequest.setUser("updater");
+
+        Map<String, Object> metadata = new HashMap();
+        metadata.put("notes", "updatedNote");
+        metadata.put("labels", Arrays.asList("updatedLabel1", "updatedLabel2"));
+        updateRequest.setMetadata(metadata);
+        return updateRequest;
+    }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/SpecificationServiceTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/SpecificationServiceTest.java
new file mode 100644 (file)
index 0000000..dff0727
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service;
+
+import org.onap.dcaegen2.platform.mod.model.specification.DeploymentType;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.SpecificationRequest;
+import org.onap.dcaegen2.platform.mod.model.specification.Specification;
+import org.onap.dcaegen2.platform.mod.model.specification.SpecificationStatus;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationGateway;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationServiceImpl;
+import org.onap.dcaegen2.platform.mod.web.service.specification.SpecificationValidatorService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother.MS_INSTANCE_ID;
+import static org.onap.dcaegen2.platform.mod.objectmothers.SpecificationObjectMother.getMockSpecification;
+import static org.onap.dcaegen2.platform.mod.objectmothers.SpecificationObjectMother.getSpecificationRequest;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith({MockitoExtension.class})
+public class SpecificationServiceTest {
+
+    private SpecificationServiceImpl service;
+
+    @Mock
+    private SpecificationGateway specRepo;
+
+    @Mock
+    private MsInstanceService msInstanceService;
+
+    @Mock
+    private SpecificationValidatorService validatorService;
+
+    @BeforeEach
+    void setUp() {
+        service = new SpecificationServiceImpl();
+        service.setMsInstanceService(msInstanceService);
+        service.setSpecificationValidatorService(validatorService);
+        service.setSpecificationGateway(specRepo);
+    }
+
+    @Test
+    void createSpecificationTest() throws Exception {
+        //given
+        SpecificationRequest request = getSpecificationRequest();
+        Specification specFromRepo = getMockSpecification(DeploymentType.K8S);
+        MsInstance msInstance = MsInstanceObjectMother.createMsInstance();
+
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstance);
+        when(specRepo.save(any(Specification.class))).thenReturn(specFromRepo);
+
+        //when
+        Specification spec = service.createSpecification(MS_INSTANCE_ID, request);
+
+        //then
+        assertThatFieldsAreCorrect(request, spec);
+        verifyCalls(request, msInstance);
+
+    }
+
+    private void assertThatFieldsAreCorrect(SpecificationRequest request, Specification spec) {
+        assertThat(spec.getStatus()).isEqualTo(SpecificationStatus.ACTIVE);
+        assertThat(spec.getSpecContent()).isEqualTo(request.getSpecContent());
+        assertThat(spec.getPolicyJson()).isEqualTo(request.getPolicyJson());
+        assertThat(spec.getType()).isEqualTo(request.getType());
+        assertThat(spec.getMetadata().get("createdBy")).isEqualTo(request.getUser());
+        assertThat(spec.getMetadata().get("createdOn")).isNotNull();
+        assertThat(spec.getMsInstanceInfo()).isNotNull();
+    }
+
+    private void verifyCalls(SpecificationRequest request, MsInstance msInstance) {
+        verify(msInstanceService, times(1)).getMsInstanceById(MS_INSTANCE_ID);
+        verify(validatorService, times(1)).validateSpecForRelease(request, msInstance.getRelease());
+        verify(specRepo, times(2)).save(any(Specification.class));
+        verify(msInstanceService, times(1)).updateMsInstance(msInstance);
+    }
+}
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/ArtifactFileNameCreatorTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/ArtifactFileNameCreatorTest.java
new file mode 100644 (file)
index 0000000..12a7dd2
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.specification.DeploymentType;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.BlueprintFileNameCreateException;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+
+class ArtifactFileNameCreatorTest {
+
+    private ArtifactFileNameCreator fileNameCreator;
+
+    @BeforeEach
+    void setUp() {
+        fileNameCreator = new ArtifactFileNameCreator();
+    }
+
+    @Test
+    void test_createCorrectBlueprintFileName() throws Exception{
+        //arrange
+        MsInstance msInstance = MsInstanceObjectMother.createMsInstance();
+        String expectedName = createExpectedName(msInstance);
+
+        //act
+        String fileName = fileNameCreator.createFileName(msInstance, 1);
+
+        //assert
+        Assertions.assertThat(fileName).isEqualTo(expectedName);
+    }
+
+    @Test
+    void test_missingTagForFileNameCreation_ShouldRaiseException() throws Exception{
+
+        //arrange
+        MsInstance msInstance = MsInstanceObjectMother.createMsInstance();
+        msInstance.setMsInfo(new HashMap<>());
+
+        Assertions.assertThatExceptionOfType(BlueprintFileNameCreateException.class).isThrownBy(
+                () -> fileNameCreator.createFileName(msInstance, 1)
+        );
+    }
+
+    @Test
+    void test_missingSpecForFileNameCreation_ShouldRaiseException() throws Exception{
+
+        //arrange
+        MsInstance msInstance = MsInstanceObjectMother.createMsInstance();
+        msInstance.setActiveSpec(null);
+
+        Assertions.assertThatExceptionOfType(BlueprintFileNameCreateException.class).isThrownBy(
+                () -> fileNameCreator.createFileName(msInstance, 1)
+        );
+    }
+
+
+    private String createExpectedName(MsInstance msInstance) {
+        String fileName = MsInstanceObjectMother.BASE_MS_TAG + "_"
+                + DeploymentType.DOCKER.toString().toLowerCase() + "_"
+                + msInstance.getRelease() + "_"
+                + "1"
+                + ".yaml";
+
+        return fileName;
+    }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactServiceImplTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactServiceImplTest.java
new file mode 100644 (file)
index 0000000..749d8b1
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.DeploymentArtifactNotFound;
+import org.onap.dcaegen2.platform.mod.model.microserviceinstance.MsInstance;
+import org.onap.dcaegen2.platform.mod.model.restapi.DeploymentArtifactPatchRequest;
+import org.onap.dcaegen2.platform.mod.objectmothers.DeploymentArtifactObjectMother;
+import org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother;
+import org.onap.dcaegen2.platform.mod.web.service.microserviceinstance.MsInstanceService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.onap.dcaegen2.platform.mod.objectmothers.MsInstanceObjectMother.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class DeploymentArtifactServiceImplTest {
+
+    private DeploymentArtifactServiceImpl deploymentArtifactService;
+
+    @Mock
+    private MsInstanceService msInstanceService;
+
+    @Mock
+    private DeploymentArtifactGeneratorStrategy deploymentArtifactGeneratorStrategy;
+
+    @Mock
+    private DeploymentArtifactGateway repository;
+
+    @Mock
+    private ArtifactFileNameCreator fileNameCreator;
+
+    @Mock
+    private DeploymentArtifactStatusChangeHandler deploymentArtifactStatusChangeHandler;
+
+    private MsInstance msInstance;
+
+    DeploymentArtifact deploymentArtifact;
+
+    @BeforeEach
+    void setUp() {
+        //Initiated the deployment artifact core with mocks
+        deploymentArtifactService = new DeploymentArtifactServiceImpl();
+        deploymentArtifactService.setDeploymentArtifactGeneratorStrategy(deploymentArtifactGeneratorStrategy);
+        deploymentArtifactService.setDeploymentArtifactGateway(repository);
+        deploymentArtifactService.setMsInstanceService(msInstanceService);
+        deploymentArtifactService.setFileNameCreator(fileNameCreator);
+        deploymentArtifactService.setStatusChangeHandler(deploymentArtifactStatusChangeHandler);
+    }
+
+    private void setupMockBehaviours() {
+        //Mock methods
+        deploymentArtifact = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        msInstance = MsInstanceObjectMother.createMsInstance();
+
+        when(deploymentArtifactGeneratorStrategy.generateForRelease(msInstance.getActiveSpec(), msInstance.getRelease()))
+                .thenReturn(DeploymentArtifactObjectMother.createBlueprintResponse());
+        when(repository.save(any())).thenReturn(deploymentArtifact);
+        when(fileNameCreator.createFileName(any(MsInstance.class), any(Integer.class))).thenReturn(BASE_MS_TAG + "_" +
+                msInstance.getActiveSpec().getType().toString().toLowerCase() + "_" + msInstance.getRelease() + "_1.yaml");
+    }
+
+    @Test
+    void test_getAllDeploymentArtifactInstance() throws Exception{
+        when(repository.findAll()).thenReturn(Arrays.asList(deploymentArtifact));
+        List<DeploymentArtifact> deployments = deploymentArtifactService.getAllDeploymentArtifacts();
+        assertThat(deployments.size()).isEqualTo(1);
+    }
+
+    @Test
+    void test_GenerateBlueprint_shouldReturnCorrectBlueprint() throws Exception{
+
+        setupMockBehaviours();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstance);
+
+        //act
+        DeploymentArtifact resultDAO = deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+
+        //assert
+        verify(msInstanceService, atLeastOnce()).getMsInstanceById(MS_INSTANCE_ID);
+        verify(repository, times(1)).save(any());
+        assertThat(resultDAO.getContent()).contains("tosca_definitions_version");
+        assertThat(resultDAO.getId()).isNotEmpty();
+        assertThat(resultDAO.getVersion()).isEqualTo(1);
+        assertThat(resultDAO.getStatus()).isEqualTo(DeploymentArtifactStatus.IN_DEV);
+        assertThat(resultDAO.getMsInstanceInfo().getId()).isEqualTo(MS_INSTANCE_ID);
+        assertThat(resultDAO.getMsInstanceInfo().getName()).isEqualTo(msInstance.getName());
+        assertThat(resultDAO.getMsInstanceInfo().getRelease()).isEqualTo(msInstance.getRelease());
+        assertThat(resultDAO.getSpecificationInfo().get("id")).isNotNull();
+        assertThat(resultDAO.getMetadata().get("createdBy")).isEqualTo(USER);
+
+    }
+
+    @Test
+    void test_deploymentVersionIsInitatedWith1() throws Exception{
+        setupMockBehaviours();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstance);
+        DeploymentArtifact resultDAO = deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+        assertThat(resultDAO.getVersion()).isEqualTo(1);
+    }
+
+    @Test
+    void test_deploymentVersionIncrementsForEachAddForAnInstance() throws Exception{
+        setupMockBehaviours();
+        MsInstance msInstanceWithRef = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstanceWithRef);
+
+        DeploymentArtifact resultDAO = deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+
+        assertThat(resultDAO.getVersion()).isEqualTo(2);
+    }
+
+    @Test
+    void test_deploymentArtifactRefAddedToMsInstanceForFirstTime() throws Exception{
+        setupMockBehaviours();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstance);
+
+        DeploymentArtifact resultDAO = deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+        assertThat(msInstance.getDeploymentArtifactsInfo().getMostRecentVersion()).isEqualTo(1);
+
+        List<String> deploymentArtifactList = msInstance.getDeploymentArtifactsInfo().getDeploymentArtifacts();
+        assertThat(deploymentArtifactList.size()).isEqualTo(1);
+        assertThat(deploymentArtifactList.get(0)).isEqualTo(resultDAO.getId());
+    }
+
+    @Test
+    void test_deploymentArtifactRefAddedToMsInstanceForSecondTime() throws Exception{
+        setupMockBehaviours();
+        MsInstance msInstanceWithRef = MsInstanceObjectMother.getMsInstanceWithExistingDeploymentArtifactRef();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstanceWithRef);
+
+        DeploymentArtifact resultDAO = deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+        assertThat(msInstanceWithRef.getDeploymentArtifactsInfo().getMostRecentVersion()).isEqualTo(2);
+
+        List<String> deploymentArtifactList = msInstanceWithRef.getDeploymentArtifactsInfo().getDeploymentArtifacts();
+        assertThat(deploymentArtifactList.size()).isEqualTo(2);
+        assertThat(deploymentArtifactList.get(1)).isEqualTo(resultDAO.getId());
+    }
+
+    @Test
+    void test_ifMsInstanceIsPersistedAfterDeploymentArtifactCreation() throws Exception{
+        setupMockBehaviours();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstance);
+        deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+        verify(msInstanceService, times(1)).updateMsInstance(msInstance);
+    }
+
+    @Test
+    void test_blueprintFileNameValidation() throws Exception{
+        setupMockBehaviours();
+        when(msInstanceService.getMsInstanceById(MS_INSTANCE_ID)).thenReturn(msInstance);
+        DeploymentArtifact resultDAO = deploymentArtifactService.generateDeploymentArtifact(MS_INSTANCE_ID, USER);
+        System.out.println(resultDAO.getFileName());
+        assertThat(resultDAO.getFileName().contains(BASE_MS_TAG)).isTrue();
+    }
+
+    @Test
+    void test_updateStatusForDeploymentArtifact() throws Exception{
+        //arrange
+        deploymentArtifact = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+
+        DeploymentArtifactPatchRequest dtoWithStatus = new DeploymentArtifactPatchRequest();
+        dtoWithStatus.setStatus(DeploymentArtifactStatus.NOT_NEEDED);
+
+        when(repository.findById("id-123")).thenReturn(Optional.of(deploymentArtifact));
+
+            //Mocking void method from DeploymentArtifactStatusChangeHandler
+        doAnswer(invocation -> {
+            deploymentArtifact.setStatus(DeploymentArtifactStatus.NOT_NEEDED);
+            return null;
+        }).when(deploymentArtifactStatusChangeHandler).handleStatusChange(dtoWithStatus.getStatus(), deploymentArtifact);
+
+        //act
+        deploymentArtifactService.updateDeploymentArtifact("id-123", dtoWithStatus, "user1");
+
+        //assert
+        assertThat(deploymentArtifact.getStatus()).isEqualTo(DeploymentArtifactStatus.NOT_NEEDED);
+        assertThat(deploymentArtifact.getMetadata().get("updatedBy")).isEqualTo("user1");
+        assertThat(deploymentArtifact.getMetadata().get("updatedOn")).isNotNull();
+
+        verify(deploymentArtifactStatusChangeHandler, times(1)).handleStatusChange(dtoWithStatus.getStatus(),
+                deploymentArtifact);
+        verify(msInstanceService, times(1)).
+                updateStatusBasedOnDeploymentArtifactsStatuses(deploymentArtifact.getMsInstanceInfo().getId());
+        verify(repository, times(1)).save(deploymentArtifact);
+    }
+
+    @Test
+    void test_findDeploymentArtifactById() throws Exception{
+        //arrange
+        deploymentArtifact = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        when(repository.findById("id-123")).thenReturn(Optional.of(deploymentArtifact));
+
+        DeploymentArtifact result = deploymentArtifactService.findDeploymentArtifactById("id-123");
+
+        assertThat(result).isEqualTo(deploymentArtifact);
+    }
+
+    @Test
+    void test_findByIdWithInvalidId() throws Exception{
+        when(repository.findById("invalid-id")).thenReturn(Optional.empty());
+        assertThatExceptionOfType(DeploymentArtifactNotFound.class).isThrownBy(
+                () -> deploymentArtifactService.findDeploymentArtifactById("invalid-id"));
+    }
+
+    @Test
+    void test_deleteDeploymentArtifact() throws Exception{
+        DeploymentArtifact deploymentArtifact =
+                DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        String id = deploymentArtifact.getId();
+
+        when(repository.findById(id)).thenReturn(Optional.of(deploymentArtifact));
+
+        deploymentArtifactService.deleteDeploymentArtifact(id);
+        verify(msInstanceService, times(1))
+                .removeDeploymentArtifactFromMsInstance(deploymentArtifact);
+        verify(repository, times(1)).deleteById(id);
+    }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactStatusChangeHandlerTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/DeploymentArtifactStatusChangeHandlerTest.java
new file mode 100644 (file)
index 0000000..5e9acfa
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.model.exceptions.deploymentartifact.StatusChangeNotValidException;
+import org.onap.dcaegen2.platform.mod.objectmothers.DeploymentArtifactObjectMother;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class DeploymentArtifactStatusChangeHandlerTest {
+
+    DeploymentArtifactStatusChangeHandler artifactStatusChangeHandler;
+
+    @Mock
+    DeploymentArtifactService deploymentArtifactService;
+
+    @BeforeEach
+    void setUp() {
+        artifactStatusChangeHandler = new DeploymentArtifactStatusChangeHandler();
+        artifactStatusChangeHandler.setDeploymentArtifactService(deploymentArtifactService);
+    }
+
+    @Test
+    void test_DevCompleteToNotNeeded() throws Exception{
+        //arrange
+        List<DeploymentArtifact> mockDeploymentArticats = DeploymentArtifactObjectMother.createMockDeploymentArtifactsWithDifferentStatuses(true);
+        DeploymentArtifact givenDAO = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.DEV_COMPLETE);
+        String msInstaneId = givenDAO.getMsInstanceInfo().getId();
+
+        when(deploymentArtifactService.findByMsInstanceId(msInstaneId)).thenReturn(mockDeploymentArticats);
+
+        //act
+        artifactStatusChangeHandler.handleStatusChange(DeploymentArtifactStatus.NOT_NEEDED, givenDAO);
+
+        assertThat(givenDAO.getStatus()).isEqualTo(DeploymentArtifactStatus.NOT_NEEDED);
+//        verify(msInstanceStatusChangeHandler, times(1))
+//                .updateStatusBasedOnDeploymentArtifactsStatuses(msInstaneId);
+
+    }
+
+    @Test
+    void test_ValidateIfArtifactWithDevCompleteStatusNotFoundForTheSameInstance() throws Exception{
+        //arrange
+        DeploymentArtifact givenDAO = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        String msInstaneId = givenDAO.getMsInstanceInfo().getId();
+
+        List<DeploymentArtifact> mockDeploymentArticats = DeploymentArtifactObjectMother.createMockDeploymentArtifactsWithDifferentStatuses(false);
+        when(deploymentArtifactService.findByMsInstanceId(msInstaneId)).thenReturn(mockDeploymentArticats);
+
+        //act
+        artifactStatusChangeHandler.handleStatusChange(DeploymentArtifactStatus.DEV_COMPLETE, givenDAO);
+
+        //assert
+        assertThat(givenDAO.getStatus()).isEqualTo(DeploymentArtifactStatus.DEV_COMPLETE);
+        verify(deploymentArtifactService, times(1)).
+                findByMsInstanceId(givenDAO.getMsInstanceInfo().getId());
+//        verify(msInstanceStatusChangeHandler, times(1))
+//                .updateStatusBasedOnDeploymentArtifactsStatuses(msInstaneId);
+
+    }
+
+    @Test
+    void DoesntValidateIfArtifactWithDevCompleteStatusAlreadyExistsForTheSameInstance() throws Exception{
+        //arrange
+        DeploymentArtifact givenDAO = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        List<DeploymentArtifact> mockDeploymentArticats = DeploymentArtifactObjectMother.createMockDeploymentArtifactsWithDifferentStatuses(true);
+        when(deploymentArtifactService.findByMsInstanceId("id-123")).thenReturn(mockDeploymentArticats);
+
+        //act/assert
+        assertThatExceptionOfType(StatusChangeNotValidException.class).isThrownBy(
+                () -> artifactStatusChangeHandler.handleStatusChange(DeploymentArtifactStatus.DEV_COMPLETE, givenDAO)
+        );
+
+    }
+
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/SearchDeploymentArtifactsTest.java b/mod2/catalog-service/src/test/java/org/onap/dcaegen2/platform/mod/web/service/deploymentartifact/SearchDeploymentArtifactsTest.java
new file mode 100644 (file)
index 0000000..bc1a3a0
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  org.onap.dcae
+ *  ================================================================================
+ *  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+package org.onap.dcaegen2.platform.mod.web.service.deploymentartifact;
+
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifact;
+import org.onap.dcaegen2.platform.mod.model.deploymentartifact.DeploymentArtifactStatus;
+import org.onap.dcaegen2.platform.mod.objectmothers.DeploymentArtifactObjectMother;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class SearchDeploymentArtifactsTest {
+
+    private DeploymentArtifactServiceImpl service;
+
+    @Mock
+    private DeploymentArtifactGateway repository;
+
+    private DeploymentArtifact artifact_1;
+    private DeploymentArtifact artifact_2;
+    private DeploymentArtifact artifact_3;
+
+    @BeforeEach
+    void setUp() {
+        service = new DeploymentArtifactServiceImpl();
+        service.setDeploymentArtifactGateway(repository);
+
+        //given
+        artifact_1 = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.IN_DEV);
+        artifact_1.getMsInstanceInfo().setRelease("2008");
+        artifact_2 = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.DEV_COMPLETE);
+        artifact_2.getMsInstanceInfo().setRelease("2010");
+        artifact_3 = DeploymentArtifactObjectMother.createDeploymentArtifactDAO(DeploymentArtifactStatus.DEV_COMPLETE);
+        artifact_3.getMsInstanceInfo().setRelease("2008");
+
+    }
+
+//    @Test
+//    void findArtifacts_filteredWithRelease() throws Exception {
+//        List<DeploymentArtifact> artifacts = Arrays.asList(artifact_1, artifact_3);
+//        when(repository.findByReleaseOrStatusOfMsInstance("2008", null)).thenReturn(artifacts);
+//
+//        //when
+//        DeploymentArtifactSearch search = new DeploymentArtifactSearch();
+//        DeploymentArtifactFilter filter = new DeploymentArtifactFilter();
+//        filter.setRelease("2008");
+//        search.setFilter(filter);
+//
+//        List<DeploymentArtifact> result = core.searchDeploymentArtifacts(search);
+//
+//        //assert
+//        assertThat(result.size()).isEqualTo(2);
+//    }
+//
+//    @Test
+//    void findArtifacts_filteredWithStatus() throws Exception{
+//        List<DeploymentArtifact> artifacts = Arrays.asList(artifact_2, artifact_3);
+//        when(repository.findByReleaseOrStatusOfMsInstance(null, DeploymentArtifactStatus.DEV_COMPLETE))
+//                .thenReturn(artifacts);
+//
+//        DeploymentArtifactSearch search = new DeploymentArtifactSearch();
+//        DeploymentArtifactFilter filter = new DeploymentArtifactFilter();
+//        filter.setStatus(DeploymentArtifactStatus.DEV_COMPLETE);
+//        search.setFilter(filter);
+//
+//        List<DeploymentArtifact> result = core.searchDeploymentArtifacts(search);
+//        assertThat(result.size()).isEqualTo(2);
+//
+//    }
+}
diff --git a/mod2/catalog-service/src/test/resources/application.properties b/mod2/catalog-service/src/test/resources/application.properties
new file mode 100644 (file)
index 0000000..d6a913c
--- /dev/null
@@ -0,0 +1 @@
+#spring.data.mongodb.port=0
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/resources/http/requests/CreateSpecificationRequest.json b/mod2/catalog-service/src/test/resources/http/requests/CreateSpecificationRequest.json
new file mode 100644 (file)
index 0000000..a0465d8
--- /dev/null
@@ -0,0 +1,180 @@
+{
+  "specContent": {
+    "self": {
+      "component_type": "docker",
+      "description": "Hello World mS for subscribing the data from local DMaaP, DR or MR, processing them and publishing them as PM files to local DMaaP DR",
+      "name": "dcae-collectors-vcc-helloworld-pm",
+      "version": "1.0.1"
+    },
+    "services": {
+      "calls": [],
+      "provides": []
+    },
+    "streams": {
+      "publishes": [
+        {
+          "config_key": "DCAE-HELLO-WORLD-PUB-DR",
+          "format": "dataformat_Hello_World_PM",
+          "type": "data_router",
+          "version": "1.0.0"
+        },
+        {
+          "config_key": "DCAE-HELLO-WORLD-PUB-MR",
+          "format": "dataformat_Hello_World_PM",
+          "type": "message_router",
+          "version": "1.0.0"
+        }
+      ],
+      "subscribes": [
+        {
+          "config_key": "DCAE-HELLO-WORLD-SUB-MR",
+          "format": "dataformat_Hello_World_PM",
+          "route": "/DCAE_HELLO_WORLD_SUB_MR",
+          "type": "message_router",
+          "version": "1.0.0"
+        },
+        {
+          "config_key": "DCAE-HELLO-WORLD-SUB-DR",
+          "format": "dataformat_Hello_World_PM",
+          "route": "/DCAE-HELLO-WORLD-SUB-DR",
+          "type": "data_router",
+          "version": "1.0.0"
+        }
+      ]
+    },
+    "parameters": [
+      {
+        "name": "vcc_hello_name",
+        "value": "120",
+        "type": "integer",
+        "description": "the name entered for specific person",
+        "sourced_at_deployment": false,
+        "designer_editable": false,
+        "policy_editable": false
+      },
+      {
+        "name": "useDtiConfig",
+        "value": false,
+        "type": "boolean",
+        "description": "component depends on configuration from dti.",
+        "sourced_at_deployment": false,
+        "designer_editable": true,
+        "policy_editable": false,
+        "required": true
+      },
+      {
+        "name": "isSelfServeComponent",
+        "value": false,
+        "type": "boolean",
+        "description": "Is this used as self serve component.",
+        "sourced_at_deployment": false,
+        "designer_editable": true,
+        "policy_editable": false,
+        "required": true
+      }
+    ],
+    "auxilary": {
+      "healthcheck": {
+        "interval": "60s",
+        "initialDelaySeconds": "120s",
+        "timeout": "20s",
+        "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+        "type": "docker"
+      },
+      "livehealthcheck": {
+        "interval": "60s",
+        "initialDelaySeconds": "120s",
+        "timeout": "20s",
+        "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+        "type": "docker"
+      },
+      "reconfigs": {
+        "app_reconfig": "abc"
+      },
+      "volumes": [
+        {
+          "container": {
+            "bind": "/opt/app/dcae-certificate"
+          },
+          "host": {
+            "path": "/opt/app/dcae-certificate"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/logs/DCAE/dmd/AGENT"
+          },
+          "host": {
+            "path": "/opt/logs/DCAE/helloworldpm/dmd/AGENT"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/logs/DCAE/dmd/WATCHER"
+          },
+          "host": {
+            "path": "/opt/logs/DCAE/helloworldpm/dmd/WATCHER"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/app/vcc/logs/DCAE"
+          },
+          "host": {
+            "path": "/opt/logs/DCAE/helloworldpm/vcc-logs"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/app/vcc/archive/data"
+          },
+          "host": {
+            "path": "/opt/data/DCAE/helloworldpm/vcc-archive"
+          }
+        }
+      ]
+    },
+    "artifacts": [
+      {
+        "type": "docker image",
+        "uri": "dockercentral.it.att.com:5100/com.att.sample/dcae-controller-vcc-helloworld-pm:18.02-001"
+      }
+    ]
+  },
+  "policyJson": {
+    "policies": [
+      {
+        "configAttributes": "",
+        "configName": "",
+        "onapName": "DCAE",
+        "policyName": "DCAE.Config_",
+        "unique": false
+      },
+      {
+        "onapName": "DCAE",
+        "policyName": "DCAE.Config_",
+        "unique": true
+      },
+      {
+        "configAttributes": "",
+        "configName": "",
+        "onapName": "DCAE",
+        "policyName": "DCAE.Config_*",
+        "unique": false
+      }
+    ],
+    "policy": [
+      {
+        "policy_id": "id_0"
+      }
+    ]
+  },
+  "type": "K8S",
+  "user": "abc123",
+  "metadata": {
+    "notes": "Sample 812-1",
+    "labels": [
+      "LATEST"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/resources/http/requests/CreateSpecificationResponse.json b/mod2/catalog-service/src/test/resources/http/requests/CreateSpecificationResponse.json
new file mode 100644 (file)
index 0000000..3cb6dcf
--- /dev/null
@@ -0,0 +1,188 @@
+{
+  "id": "5f3417a26aef7f07bc91350a",
+  "status": "ACTIVE",
+  "specContent": {
+    "self": {
+      "component_type": "docker",
+      "description": "Hello World mS for subscribing the data from local DMaaP, DR or MR, processing them and publishing them as PM files to local DMaaP DR",
+      "name": "dcae-collectors-vcc-helloworld-pm",
+      "version": "1.0.1"
+    },
+    "services": {
+      "calls": [],
+      "provides": []
+    },
+    "streams": {
+      "publishes": [
+        {
+          "config_key": "DCAE-HELLO-WORLD-PUB-DR",
+          "format": "dataformat_Hello_World_PM",
+          "type": "data_router",
+          "version": "1.0.0"
+        },
+        {
+          "config_key": "DCAE-HELLO-WORLD-PUB-MR",
+          "format": "dataformat_Hello_World_PM",
+          "type": "message_router",
+          "version": "1.0.0"
+        }
+      ],
+      "subscribes": [
+        {
+          "config_key": "DCAE-HELLO-WORLD-SUB-MR",
+          "format": "dataformat_Hello_World_PM",
+          "route": "/DCAE_HELLO_WORLD_SUB_MR",
+          "type": "message_router",
+          "version": "1.0.0"
+        },
+        {
+          "config_key": "DCAE-HELLO-WORLD-SUB-DR",
+          "format": "dataformat_Hello_World_PM",
+          "route": "/DCAE-HELLO-WORLD-SUB-DR",
+          "type": "data_router",
+          "version": "1.0.0"
+        }
+      ]
+    },
+    "parameters": [
+      {
+        "name": "vcc_hello_name",
+        "value": "120",
+        "type": "integer",
+        "description": "the name entered for specific person",
+        "sourced_at_deployment": false,
+        "designer_editable": false,
+        "policy_editable": false
+      },
+      {
+        "name": "useDtiConfig",
+        "value": false,
+        "type": "boolean",
+        "description": "component depends on configuration from dti.",
+        "sourced_at_deployment": false,
+        "designer_editable": true,
+        "policy_editable": false,
+        "required": true
+      },
+      {
+        "name": "isSelfServeComponent",
+        "value": false,
+        "type": "boolean",
+        "description": "Is this used as self serve component.",
+        "sourced_at_deployment": false,
+        "designer_editable": true,
+        "policy_editable": false,
+        "required": true
+      }
+    ],
+    "auxilary": {
+      "healthcheck": {
+        "interval": "60s",
+        "initialDelaySeconds": "120s",
+        "timeout": "20s",
+        "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+        "type": "docker"
+      },
+      "livehealthcheck": {
+        "interval": "60s",
+        "initialDelaySeconds": "120s",
+        "timeout": "20s",
+        "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+        "type": "docker"
+      },
+      "reconfigs": {
+        "app_reconfig": "abc"
+      },
+      "volumes": [
+        {
+          "container": {
+            "bind": "/opt/app/dcae-certificate"
+          },
+          "host": {
+            "path": "/opt/app/dcae-certificate"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/logs/DCAE/dmd/AGENT"
+          },
+          "host": {
+            "path": "/opt/logs/DCAE/helloworldpm/dmd/AGENT"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/logs/DCAE/dmd/WATCHER"
+          },
+          "host": {
+            "path": "/opt/logs/DCAE/helloworldpm/dmd/WATCHER"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/app/vcc/logs/DCAE"
+          },
+          "host": {
+            "path": "/opt/logs/DCAE/helloworldpm/vcc-logs"
+          }
+        },
+        {
+          "container": {
+            "bind": "/opt/app/vcc/archive/data"
+          },
+          "host": {
+            "path": "/opt/data/DCAE/helloworldpm/vcc-archive"
+          }
+        }
+      ]
+    },
+    "artifacts": [
+      {
+        "type": "docker image",
+        "uri": "dockercentral.it.att.com:5100/com.att.sample/dcae-controller-vcc-helloworld-pm:18.02-001"
+      }
+    ]
+  },
+  "policyJson": {
+    "policies": [
+      {
+        "configAttributes": "",
+        "configName": "",
+        "onapName": "DCAE",
+        "policyName": "DCAE.Config_",
+        "unique": false
+      },
+      {
+        "onapName": "DCAE",
+        "policyName": "DCAE.Config_",
+        "unique": true
+      },
+      {
+        "configAttributes": "",
+        "configName": "",
+        "onapName": "DCAE",
+        "policyName": "DCAE.Config_*",
+        "unique": false
+      }
+    ],
+    "policy": [
+      {
+        "policy_id": "id_0"
+      }
+    ]
+  },
+  "type": "K8S",
+  "metadata": {
+    "notes": "Sample 812-1",
+    "labels": [
+      "LATEST"
+    ],
+    "createdBy": "abc123",
+    "createdOn": "2020-08-12T16:24:02.621+0000"
+  },
+  "msInstanceInfo": {
+    "release": "2006",
+    "name": "hello-804",
+    "id": "5f1592ffb551b604628316b0"
+  }
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/resources/specification/componentSpec_hello_world-with-dmaap.json b/mod2/catalog-service/src/test/resources/specification/componentSpec_hello_world-with-dmaap.json
new file mode 100644 (file)
index 0000000..0ad6b6e
--- /dev/null
@@ -0,0 +1,149 @@
+{
+       "self": {
+               "component_type": "docker",
+               "description": "Hello World mS for subscribing the data from local DMaaP, DR or MR, processing them and publishing them as PM files to local DMaaP DR",
+               "name": "dcae-collectors-vcc-helloworld-pm",
+               "version": "1.0.1"
+       },
+
+       "services": {
+               "calls": [],
+               "provides": []
+       },
+
+       "streams": {
+               "publishes": [{
+                       "config_key": "DCAE-HELLO-WORLD-PUB-DR",
+                       "format": "dataformat_Hello_World_PM",
+                       "type": "data_router",
+                       "version": "1.0.0"
+               },
+                       {
+                               "config_key": "DCAE-HELLO-WORLD-PUB-MR",
+                               "format": "dataformat_Hello_World_PM",
+                               "type": "message_router",
+                               "version": "1.0.0"
+                       }
+               ],
+
+               "subscribes": [{
+                       "config_key": "DCAE-HELLO-WORLD-SUB-MR",
+                       "format": "dataformat_Hello_World_PM",
+                       "route": "/DCAE_HELLO_WORLD_SUB_MR",
+                       "type": "message_router",
+                       "version": "1.0.0"
+               },
+                       {
+                               "config_key": "DCAE-HELLO-WORLD-SUB-DR",
+                               "format": "dataformat_Hello_World_PM",
+                               "route": "/DCAE-HELLO-WORLD-SUB-DR",
+                               "type": "data_router",
+                               "version": "1.0.0"
+                       }
+               ]
+       },
+
+       "parameters":
+       [
+               {
+                       "name": "vcc_hello_name",
+                       "value": "120",
+                       "type": "integer",
+                       "description": "the name entered for specific person",
+                       "sourced_at_deployment": false,
+                       "designer_editable": false,
+                       "policy_editable": false
+               },
+
+               {
+                       "name": "useDtiConfig",
+                       "value": false,
+                       "type" : "boolean",
+                       "description": "component depends on configuration from dti.",
+                       "sourced_at_deployment": false,
+                       "designer_editable": true,
+                       "policy_editable": false,
+                       "required" : true
+               },
+
+               {
+                       "name": "isSelfServeComponent",
+                       "value": false,
+                       "type": "boolean",
+                       "description": "Is this used as self serve component.",
+                       "sourced_at_deployment": false,
+                       "designer_editable": true,
+                       "policy_editable": false,
+                       "required" : true
+               }
+       ],
+
+       "auxilary": {
+               "healthcheck": {
+                       "interval": "60s",
+                       "initialDelaySeconds": "120s",
+                       "timeout": "20s",
+                       "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+                       "type": "docker"
+               },
+               "livehealthcheck": {
+                       "interval": "60s",
+                       "initialDelaySeconds": "120s",
+                       "timeout": "20s",
+                       "script": "/opt/app/vcc/bin/common/HealthCheck_HelloWorld.sh",
+                       "type": "docker"
+               },
+               "reconfigs":{
+                       "app_reconfig" : "abc"
+               },
+
+               "volumes": [
+                       {
+                               "container": {
+                                       "bind": "/opt/app/dcae-certificate"
+                               },
+                               "host": {
+                                       "path": "/opt/app/dcae-certificate"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/logs/DCAE/dmd/AGENT"
+                               },
+                               "host": {
+                                       "path": "/opt/logs/DCAE/helloworldpm/dmd/AGENT"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/logs/DCAE/dmd/WATCHER"
+                               },
+                               "host": {
+                                       "path": "/opt/logs/DCAE/helloworldpm/dmd/WATCHER"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/app/vcc/logs/DCAE"
+                               },
+                               "host": {
+                                       "path": "/opt/logs/DCAE/helloworldpm/vcc-logs"
+                               }
+                       },
+                       {
+                               "container": {
+                                       "bind": "/opt/app/vcc/archive/data"
+                               },
+                               "host": {
+                                       "path": "/opt/data/DCAE/helloworldpm/vcc-archive"
+                               }
+                       }
+
+               ]
+
+       },
+       "artifacts": [{
+               "type": "docker image",
+               "uri": "dockercentral.it.att.com:5100/com.att.sample/dcae-controller-vcc-helloworld-pm:18.02-001"
+       }]
+}
\ No newline at end of file
diff --git a/mod2/catalog-service/src/test/resources/specification/policy_json_sample_3.json b/mod2/catalog-service/src/test/resources/specification/policy_json_sample_3.json
new file mode 100644 (file)
index 0000000..4a8c76f
--- /dev/null
@@ -0,0 +1,28 @@
+{
+       "policies": [
+               {
+                       "configAttributes": "",
+               "configName": "",
+               "onapName": "DCAE",
+               "policyName": "DCAE.Config_",
+               "unique": false
+               },
+               {
+                       "onapName": "DCAE",
+                       "policyName": "DCAE.Config_",
+                       "unique": true
+               },
+               {
+                       "configAttributes": "",
+                       "configName": "",
+                       "onapName": "DCAE",
+                       "policyName": "DCAE.Config_*",
+                       "unique": false
+               }
+       ],
+       "policy": [
+               {
+                       "policy_id" : "id_0"
+               }
+       ]
+}
\ No newline at end of file
diff --git a/mod2/ui/.editorconfig b/mod2/ui/.editorconfig
new file mode 100644 (file)
index 0000000..e89330a
--- /dev/null
@@ -0,0 +1,13 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/mod2/ui/.gitignore b/mod2/ui/.gitignore
new file mode 100644 (file)
index 0000000..f4f46a5
--- /dev/null
@@ -0,0 +1,46 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+# Only exists if Bazel was run
+/bazel-out
+
+# dependencies
+/node_modules
+
+# profiling files
+chrome-profiler-events.json
+speed-measure-plugin.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/mod2/ui/Dockerfile b/mod2/ui/Dockerfile
new file mode 100644 (file)
index 0000000..67744d4
--- /dev/null
@@ -0,0 +1,26 @@
+# ---- Base Node ---- 
+FROM alpine:12.4.0 AS base 
+
+#RUN fs.inotify.max_user_watches=524288
+
+WORKDIR /app 
+
+#run in prod mode 
+# RUN ng build --prod 
+
+# copy project file 
+COPY . . 
+
+#to make healthcheck executable 
+RUN chmod +x healthcheck.sh 
+
+# COPY package.json /app 
+RUN npm install -g @angular/cli@8.0.6 
+
+#to install all dev dependencies explicitly 
+RUN npm i --only=dev 
+RUN npm install --save-dev @angular-devkit/build-angular 
+
+# RUN npm run build --prod 
+EXPOSE 4200 
+CMD ng serve --host 0.0.0.0 --disableHostCheck --proxy-config proxy.conf.json
diff --git a/mod2/ui/Jenkinsfile b/mod2/ui/Jenkinsfile
new file mode 100644 (file)
index 0000000..d51287f
--- /dev/null
@@ -0,0 +1,3 @@
+// @Library('common@develop') _
+
+pythonPipeline()
\ No newline at end of file
diff --git a/mod2/ui/README.md b/mod2/ui/README.md
new file mode 100644 (file)
index 0000000..e08fb38
--- /dev/null
@@ -0,0 +1,27 @@
+# DcaeModFe
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.1.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/mod2/ui/angular.json b/mod2/ui/angular.json
new file mode 100644 (file)
index 0000000..48a973b
--- /dev/null
@@ -0,0 +1,128 @@
+{
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+  "version": 1,
+  "newProjectRoot": "projects",
+  "projects": {
+    "dcae-mod-fe": {
+      "projectType": "application",
+      "schematics": {},
+      "root": "",
+      "sourceRoot": "src",
+      "prefix": "app",
+      "architect": {
+        "build": {
+          "builder": "@angular-builders/custom-webpack:browser",
+          "options": {
+            "customWebpackConfig": {
+              "path": "extra-webpack.config.js"
+            },
+            "outputPath": "dist/dcae-mod-fe",
+            "index": "src/index.html",
+            "main": "src/main.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "tsconfig.app.json",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "./node_modules/@angular/material/prebuilt-themes/purple-green.css",
+              "src/styles.css",
+              "./node_modules/primeng/resources/primeng.min.css",
+              "./node_modules/primeng/resources/themes/nova-light/theme.css",
+              "./node_modules/primeicons/primeicons.css"
+            ],
+            "scripts": []
+          },
+          "configurations": {
+            "production": {
+              "fileReplacements": [
+                {
+                  "replace": "src/environments/environment.ts",
+                  "with": "src/environments/environment.prod.ts"
+                }
+              ],
+              "optimization": true,
+              "outputHashing": "all",
+              "sourceMap": false,
+              "extractCss": true,
+              "namedChunks": false,
+              "aot": true,
+              "extractLicenses": true,
+              "vendorChunk": false,
+              "buildOptimizer": true,
+              "budgets": [
+                {
+                  "type": "initial",
+                  "maximumWarning": "2mb",
+                  "maximumError": "5mb"
+                }
+              ]
+            }
+          }
+        },
+        "serve": {
+          "builder": "@angular-builders/custom-webpack:dev-server",
+          "options": {
+            "browserTarget": "dcae-mod-fe:build"
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "dcae-mod-fe:build:production"
+            }
+          }
+        },
+        "extract-i18n": {
+          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "options": {
+            "browserTarget": "dcae-mod-fe:build"
+          }
+        },
+        "test": {
+          "builder": "@angular-devkit/build-angular:karma",
+          "options": {
+            "main": "src/test.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "tsconfig.spec.json",
+            "karmaConfig": "karma.conf.js",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "./node_modules/@angular/material/prebuilt-themes/purple-green.css",
+              "src/styles.css"
+            ],
+            "scripts": []
+          }
+        },
+        "lint": {
+          "builder": "@angular-devkit/build-angular:tslint",
+          "options": {
+            "tsConfig": [
+              "tsconfig.app.json",
+              "tsconfig.spec.json",
+              "e2e/tsconfig.json"
+            ],
+            "exclude": [
+              "**/node_modules/**"
+            ]
+          }
+        },
+        "e2e": {
+          "builder": "@angular-devkit/build-angular:protractor",
+          "options": {
+            "protractorConfig": "e2e/protractor.conf.js",
+            "devServerTarget": "dcae-mod-fe:serve"
+          },
+          "configurations": {
+            "production": {
+              "devServerTarget": "dcae-mod-fe:serve:production"
+            }
+          }
+        }
+      }
+    }
+  },
+  "defaultProject": "dcae-mod-fe"
+}
\ No newline at end of file
diff --git a/mod2/ui/browserslist b/mod2/ui/browserslist
new file mode 100644 (file)
index 0000000..8084853
--- /dev/null
@@ -0,0 +1,12 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# You can see what browsers were selected by your queries by running:
+#   npx browserslist
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11 # For IE 9-11 support, remove 'not'.
\ No newline at end of file
diff --git a/mod2/ui/dependenciesFile b/mod2/ui/dependenciesFile
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/mod2/ui/e2e/protractor.conf.js b/mod2/ui/e2e/protractor.conf.js
new file mode 100644 (file)
index 0000000..ff9b518
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+// @ts-check
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+/**
+ * @type { import("protractor").Config }
+ */
+exports.config = {
+  allScriptsTimeout: 11000,
+  specs: [
+    './src/**/*.e2e-spec.ts'
+  ],
+  capabilities: {
+    'browserName': 'chrome'
+  },
+  directConnect: true,
+  baseUrl: 'http://localhost:4200/',
+  framework: 'jasmine',
+  jasmineNodeOpts: {
+    showColors: true,
+    defaultTimeoutInterval: 30000,
+    print: function() {}
+  },
+  onPrepare() {
+    require('ts-node').register({
+      project: require('path').join(__dirname, './tsconfig.json')
+    });
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+  }
+};
\ No newline at end of file
diff --git a/mod2/ui/e2e/src/app.e2e-spec.ts b/mod2/ui/e2e/src/app.e2e-spec.ts
new file mode 100644 (file)
index 0000000..f25e135
--- /dev/null
@@ -0,0 +1,41 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { AppPage } from './app.po';
+import { browser, logging } from 'protractor';
+
+describe('workspace-project App', () => {
+  let page: AppPage;
+
+  beforeEach(() => {
+    page = new AppPage();
+  });
+
+  it('should display welcome message', () => {
+    page.navigateTo();
+    expect(page.getTitleText()).toEqual('Welcome to dcae-mod-fe!');
+  });
+
+  afterEach(async () => {
+    // Assert that there are no errors emitted from the browser
+    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
+    expect(logs).not.toContain(jasmine.objectContaining({
+      level: logging.Level.SEVERE,
+    } as logging.Entry));
+  });
+});
diff --git a/mod2/ui/e2e/src/app.po.ts b/mod2/ui/e2e/src/app.po.ts
new file mode 100644 (file)
index 0000000..b26c21a
--- /dev/null
@@ -0,0 +1,29 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { browser, by, element } from 'protractor';
+
+export class AppPage {
+  navigateTo() {
+    return browser.get(browser.baseUrl) as Promise<any>;
+  }
+
+  getTitleText() {
+    return element(by.css('app-root h1')).getText() as Promise<string>;
+  }
+}
diff --git a/mod2/ui/e2e/tsconfig.json b/mod2/ui/e2e/tsconfig.json
new file mode 100644 (file)
index 0000000..39b800f
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/e2e",
+    "module": "commonjs",
+    "target": "es5",
+    "types": [
+      "jasmine",
+      "jasminewd2",
+      "node"
+    ]
+  }
+}
diff --git a/mod2/ui/extra-webpack.config.js b/mod2/ui/extra-webpack.config.js
new file mode 100644 (file)
index 0000000..dd85485
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+const webpack = require('webpack')
+
+module.exports = {
+    plugins: [
+        new webpack.DefinePlugin({
+            'process.env': {
+                DCAE_HOSTNAME: JSON.stringify(process.env.DCAE_HOSTNAME)
+            }
+        })
+    ]
+}
\ No newline at end of file
diff --git a/mod2/ui/healthcheck.sh b/mod2/ui/healthcheck.sh
new file mode 100644 (file)
index 0000000..d36705b
--- /dev/null
@@ -0,0 +1,20 @@
+__='
+*  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+'
+
+#!/bin/bash 
+exit 0
\ No newline at end of file
diff --git a/mod2/ui/karma.conf.js b/mod2/ui/karma.conf.js
new file mode 100644 (file)
index 0000000..13d1962
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+  config.set({
+    basePath: '',
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-chrome-launcher'),
+      require('karma-jasmine-html-reporter'),
+      require('karma-coverage-istanbul-reporter'),
+      require('@angular-devkit/build-angular/plugins/karma')
+    ],
+    client: {
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
+    },
+    coverageIstanbulReporter: {
+      dir: require('path').join(__dirname, './coverage/dcae-mod-fe'),
+      reports: ['html', 'lcovonly', 'text-summary'],
+      fixWebpackSourcePaths: true
+    },
+    reporters: ['progress', 'kjhtml'],
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['Chrome'],
+    singleRun: false,
+    restartOnFileChange: true
+  });
+};
diff --git a/mod2/ui/package-lock.json b/mod2/ui/package-lock.json
new file mode 100644 (file)
index 0000000..f744c0b
--- /dev/null
@@ -0,0 +1,10747 @@
+{
+  "name": "dcae-mod-fe",
+  "version": "0.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@angular-builders/custom-webpack": {
+      "version": "8.4.1",
+      "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.4.1.tgz",
+      "integrity": "sha512-FbBt4mFbAxETdYLb6tTX869pIpm8nMiCpT34jROejuqLtsljymdqXhSCEWogWlel8ULAYus6BNdzZyRLyAkfqQ==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.10",
+        "ts-node": "^8.5.2",
+        "webpack-merge": "^4.2.1"
+      },
+      "dependencies": {
+        "diff": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+          "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        },
+        "source-map-support": {
+          "version": "0.5.18",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.18.tgz",
+          "integrity": "sha512-9luZr/BZ2QeU6tO2uG8N2aZpVSli4TSAOAqFOyTO51AJcD9P99c0K1h6dD6r6qo5dyT44BR5exweOaLLeldTkQ==",
+          "dev": true,
+          "requires": {
+            "buffer-from": "^1.0.0",
+            "source-map": "^0.6.0"
+          }
+        },
+        "ts-node": {
+          "version": "8.9.0",
+          "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.9.0.tgz",
+          "integrity": "sha512-rwkXfOs9zmoHrV8xE++dmNd6ZIS+nmHHCxcV53ekGJrxFLMbp+pizpPS07ARvhwneCIECPppOwbZHvw9sQtU4w==",
+          "dev": true,
+          "requires": {
+            "arg": "^4.1.0",
+            "diff": "^4.0.1",
+            "make-error": "^1.1.1",
+            "source-map-support": "^0.5.17",
+            "yn": "3.1.1"
+          }
+        },
+        "yn": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+          "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+          "dev": true
+        }
+      }
+    },
+    "@angular-builders/dev-server": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/@angular-builders/dev-server/-/dev-server-7.3.1.tgz",
+      "integrity": "sha512-rFr0NyFcwTb4RkkboYQN5JeR9ZraOkfUrQYljMSe/O01MM3SJvE8LYJbsyMwGtp71Rc8T6JrpdxaNEeYCV/4PA==",
+      "dev": true
+    },
+    "@angular-devkit/architect": {
+      "version": "0.800.6",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.800.6.tgz",
+      "integrity": "sha512-946ceRci/1yx09g8iRvULLoVihcB2RW9nhpCCMum4L9wheip8t4FWso3pd3JtPQGJV9dmsnwPzR9s12bncmj3g==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.0.6",
+        "rxjs": "6.4.0"
+      }
+    },
+    "@angular-devkit/build-angular": {
+      "version": "0.803.24",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.24.tgz",
+      "integrity": "sha512-uA789spMVghXehwAhl5zK0loY/wfxblUiL+y21T24LMCJc15a9QX5dwbXH72ioHz7qdzb/agXk7AK+foc2/0Hw==",
+      "requires": {
+        "@angular-devkit/architect": "0.803.24",
+        "@angular-devkit/build-optimizer": "0.803.24",
+        "@angular-devkit/build-webpack": "0.803.24",
+        "@angular-devkit/core": "8.3.24",
+        "@babel/core": "7.8.3",
+        "@babel/preset-env": "7.8.3",
+        "@ngtools/webpack": "8.3.24",
+        "ajv": "6.10.2",
+        "autoprefixer": "9.6.1",
+        "browserslist": "4.8.3",
+        "cacache": "12.0.2",
+        "caniuse-lite": "1.0.30001019",
+        "circular-dependency-plugin": "5.2.0",
+        "clean-css": "4.2.1",
+        "copy-webpack-plugin": "5.1.1",
+        "core-js": "3.6.4",
+        "coverage-istanbul-loader": "2.0.3",
+        "file-loader": "4.2.0",
+        "find-cache-dir": "3.0.0",
+        "glob": "7.1.4",
+        "jest-worker": "24.9.0",
+        "karma-source-map-support": "1.4.0",
+        "less": "3.9.0",
+        "less-loader": "5.0.0",
+        "license-webpack-plugin": "2.1.2",
+        "loader-utils": "1.2.3",
+        "mini-css-extract-plugin": "0.8.0",
+        "minimatch": "3.0.4",
+        "open": "6.4.0",
+        "parse5": "4.0.0",
+        "postcss": "7.0.17",
+        "postcss-import": "12.0.1",
+        "postcss-loader": "3.0.0",
+        "raw-loader": "3.1.0",
+        "regenerator-runtime": "0.13.3",
+        "rxjs": "6.4.0",
+        "sass": "1.22.9",
+        "sass-loader": "7.2.0",
+        "semver": "6.3.0",
+        "source-map": "0.7.3",
+        "source-map-loader": "0.2.4",
+        "source-map-support": "0.5.13",
+        "speed-measure-webpack-plugin": "1.3.1",
+        "style-loader": "1.0.0",
+        "stylus": "0.54.5",
+        "stylus-loader": "3.0.2",
+        "terser": "4.6.3",
+        "terser-webpack-plugin": "1.4.3",
+        "tree-kill": "1.2.2",
+        "webpack": "4.39.2",
+        "webpack-dev-middleware": "3.7.2",
+        "webpack-dev-server": "3.9.0",
+        "webpack-merge": "4.2.1",
+        "webpack-sources": "1.4.3",
+        "webpack-subresource-integrity": "1.1.0-rc.6",
+        "worker-plugin": "3.2.0"
+      },
+      "dependencies": {
+        "@angular-devkit/architect": {
+          "version": "0.803.24",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.24.tgz",
+          "integrity": "sha512-ONY/Ppzyvtb0tqgwnzQvlGlexb5nTyy58ljgL1aQLTO3cNTkpl4IQYUCTdvn61gGA+FWPAXMCCbNqOPZMsOZCQ==",
+          "requires": {
+            "@angular-devkit/core": "8.3.24",
+            "rxjs": "6.4.0"
+          }
+        },
+        "@angular-devkit/core": {
+          "version": "8.3.24",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.24.tgz",
+          "integrity": "sha512-xpT5yg+ddGDnifryBv2sRSYtq5F3iZIS+lN/K2AhhEa50B7Z+QaCVlEzoV/IfrGd6sLArdnKYwjLHFZ0LElUuw==",
+          "requires": {
+            "ajv": "6.10.2",
+            "fast-json-stable-stringify": "2.0.0",
+            "magic-string": "0.25.3",
+            "rxjs": "6.4.0",
+            "source-map": "0.7.3"
+          }
+        },
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "cacache": {
+          "version": "12.0.2",
+          "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz",
+          "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==",
+          "requires": {
+            "bluebird": "^3.5.5",
+            "chownr": "^1.1.1",
+            "figgy-pudding": "^3.5.1",
+            "glob": "^7.1.4",
+            "graceful-fs": "^4.1.15",
+            "infer-owner": "^1.0.3",
+            "lru-cache": "^5.1.1",
+            "mississippi": "^3.0.0",
+            "mkdirp": "^0.5.1",
+            "move-concurrently": "^1.0.1",
+            "promise-inflight": "^1.0.1",
+            "rimraf": "^2.6.3",
+            "ssri": "^6.0.1",
+            "unique-filename": "^1.1.1",
+            "y18n": "^4.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+          "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "magic-string": {
+          "version": "0.25.3",
+          "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz",
+          "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==",
+          "requires": {
+            "sourcemap-codec": "^1.4.4"
+          }
+        },
+        "open": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz",
+          "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==",
+          "requires": {
+            "is-wsl": "^1.1.0"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+        },
+        "source-map-support": {
+          "version": "0.5.13",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+          "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+          "requires": {
+            "buffer-from": "^1.0.0",
+            "source-map": "^0.6.0"
+          },
+          "dependencies": {
+            "source-map": {
+              "version": "0.6.1",
+              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+              "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+            }
+          }
+        },
+        "webpack-merge": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz",
+          "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==",
+          "requires": {
+            "lodash": "^4.17.5"
+          }
+        }
+      }
+    },
+    "@angular-devkit/build-optimizer": {
+      "version": "0.803.24",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.24.tgz",
+      "integrity": "sha512-Z+d7M+WpBq7AWWRwbxzb1l9O9qkylxnDRKxXvq3Tzjn43g+2WyspE91dMyrg1ISc+p8jgX6xKSblRLvtWqpA8w==",
+      "requires": {
+        "loader-utils": "1.2.3",
+        "source-map": "0.7.3",
+        "tslib": "1.10.0",
+        "typescript": "3.5.3",
+        "webpack-sources": "1.4.3"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "1.10.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+          "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
+        },
+        "typescript": {
+          "version": "3.5.3",
+          "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
+          "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g=="
+        }
+      }
+    },
+    "@angular-devkit/build-webpack": {
+      "version": "0.803.24",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.24.tgz",
+      "integrity": "sha512-Bbd5KUGaE+edN0sp8K3azuqS/JTBmeWXIumdBEtqWyL6VsohX7fL+toJlSvRkj8lg02LVyozAFetXKnyaBkfCQ==",
+      "requires": {
+        "@angular-devkit/architect": "0.803.24",
+        "@angular-devkit/core": "8.3.24",
+        "rxjs": "6.4.0"
+      },
+      "dependencies": {
+        "@angular-devkit/architect": {
+          "version": "0.803.24",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.24.tgz",
+          "integrity": "sha512-ONY/Ppzyvtb0tqgwnzQvlGlexb5nTyy58ljgL1aQLTO3cNTkpl4IQYUCTdvn61gGA+FWPAXMCCbNqOPZMsOZCQ==",
+          "requires": {
+            "@angular-devkit/core": "8.3.24",
+            "rxjs": "6.4.0"
+          }
+        },
+        "@angular-devkit/core": {
+          "version": "8.3.24",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.24.tgz",
+          "integrity": "sha512-xpT5yg+ddGDnifryBv2sRSYtq5F3iZIS+lN/K2AhhEa50B7Z+QaCVlEzoV/IfrGd6sLArdnKYwjLHFZ0LElUuw==",
+          "requires": {
+            "ajv": "6.10.2",
+            "fast-json-stable-stringify": "2.0.0",
+            "magic-string": "0.25.3",
+            "rxjs": "6.4.0",
+            "source-map": "0.7.3"
+          }
+        },
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "magic-string": {
+          "version": "0.25.3",
+          "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz",
+          "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==",
+          "requires": {
+            "sourcemap-codec": "^1.4.4"
+          }
+        }
+      }
+    },
+    "@angular-devkit/core": {
+      "version": "8.0.6",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.0.6.tgz",
+      "integrity": "sha512-gbKEVsQuYqBJPzgaxEitvs0aN9NwmUHhTkum28mRyPbS3witay/q8+3ls48M2W+98Da/PQbfndxFY4OCa+qHEA==",
+      "dev": true,
+      "requires": {
+        "ajv": "6.10.0",
+        "fast-json-stable-stringify": "2.0.0",
+        "magic-string": "0.25.2",
+        "rxjs": "6.4.0",
+        "source-map": "0.7.3"
+      }
+    },
+    "@angular-devkit/schematics": {
+      "version": "8.0.6",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.0.6.tgz",
+      "integrity": "sha512-FGPcVKxNvtdFB0A6oHyxtWeugL83nW+kPATlAimgh1hu7TCP94dDpflCV9o/lgZlH817xTYXrhToXJaMZSnDPw==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.0.6",
+        "rxjs": "6.4.0"
+      }
+    },
+    "@angular/animations": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.3.tgz",
+      "integrity": "sha512-9zciJ4YRR0bodFSYgsgXdYMz8wKKyVjch7XZADGkWubXT8mGuwlpdPMlQ6n9Cwj8Ebu0u52WxMeQsX76K9RlYA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/cdk": {
+      "version": "8.2.3",
+      "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-8.2.3.tgz",
+      "integrity": "sha512-ZwO5Sn720RA2YvBqud0JAHkZXjmjxM0yNzCO8RVtRE9i8Gl26Wk0j0nQeJkVm4zwv2QO8MwbKUKGTMt8evsokA==",
+      "requires": {
+        "parse5": "^5.0.0",
+        "tslib": "^1.7.1"
+      },
+      "dependencies": {
+        "parse5": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+          "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+          "optional": true
+        }
+      }
+    },
+    "@angular/cli": {
+      "version": "8.0.6",
+      "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.0.6.tgz",
+      "integrity": "sha512-COBpeoXyLt8FiOhsmoEnDfQcm0aTdUSUHsH3zNkVTcyxpRzZVspTDGzxhK0UsCpddXS/MMjJiXph6SJ1el3qaQ==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/architect": "0.800.6",
+        "@angular-devkit/core": "8.0.6",
+        "@angular-devkit/schematics": "8.0.6",
+        "@schematics/angular": "8.0.6",
+        "@schematics/update": "0.800.6",
+        "@yarnpkg/lockfile": "1.1.0",
+        "debug": "^4.1.1",
+        "ini": "1.3.5",
+        "inquirer": "6.3.1",
+        "npm-package-arg": "6.1.0",
+        "open": "6.2.0",
+        "pacote": "9.5.0",
+        "read-package-tree": "5.2.2",
+        "semver": "6.0.0",
+        "symbol-observable": "1.2.0",
+        "universal-analytics": "^0.4.20",
+        "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "@angular/common": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.3.tgz",
+      "integrity": "sha512-2YLYGVUf9eJZcocRmD3/9UHj4qFHt2t4ftDWJmrFM9zo2PZF+G5O9fASO7qoBbwpx3KFZtQO4dprKl2dFugRjg==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/compiler": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.3.tgz",
+      "integrity": "sha512-1/vF8D6l1O6IfWiDtaj6nC+B8CtkVtFgXgooDzLBO6XAkaCuJCnhKT1HnpWG5GtVsGaY9MGoTl1vE9ZMDbRQjg==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/compiler-cli": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.0.3.tgz",
+      "integrity": "sha512-6nckvBnnp1YnrxwCB8UKvhHAMcUa1WvGjbYMEqhgiBreiRT4ub+tKVPNzrRtQFPpcXtY1wlgOWqYHWhHRtcLlg==",
+      "dev": true,
+      "requires": {
+        "canonical-path": "1.0.0",
+        "chokidar": "^2.1.1",
+        "convert-source-map": "^1.5.1",
+        "dependency-graph": "^0.7.2",
+        "magic-string": "^0.25.0",
+        "minimist": "^1.2.0",
+        "reflect-metadata": "^0.1.2",
+        "shelljs": "^0.8.1",
+        "source-map": "^0.6.1",
+        "tslib": "^1.9.0",
+        "yargs": "13.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "get-caller-file": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+          "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+          "dev": true
+        },
+        "require-main-filename": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+          "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        },
+        "yargs": {
+          "version": "13.1.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz",
+          "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==",
+          "dev": true,
+          "requires": {
+            "cliui": "^4.0.0",
+            "find-up": "^3.0.0",
+            "get-caller-file": "^2.0.1",
+            "os-locale": "^3.1.0",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^3.0.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^13.0.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "13.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+          "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+          "dev": true,
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
+    "@angular/core": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.3.tgz",
+      "integrity": "sha512-IIxrtIPNuv2+HudER9J1nmPGiGJ4aRpeiFM9V4lSiSFv50RzuaoG60XqYIpUyuBdgvyKigcrfSbu9+x1vyN0hw==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/forms": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.3.tgz",
+      "integrity": "sha512-22s82QDRQ72K4vMYuNh3NAN+da9uanwoydnfKlp2rb9dZAb2QVX9NN6gSoMrkSSr2O9KTP6pWiw6A3/MW8sGRA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/language-service": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.0.3.tgz",
+      "integrity": "sha512-04XojOo9FJgEQE/rZafnaJQxPEU+//TSzTgpGoIVzCSMx+joCY/ZSSwJZPWxiHlOE57W/zX02ZY+TwcM81oTdw==",
+      "dev": true
+    },
+    "@angular/material": {
+      "version": "8.2.3",
+      "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.2.3.tgz",
+      "integrity": "sha512-SOczkIaqes+r+9XF/UUiokidfFKBpHkOPIaFK857sFD0FBNPvPEpOr5oHKCG3feERRwAFqHS7Wo2ohVEWypb5A==",
+      "requires": {
+        "tslib": "^1.7.1"
+      }
+    },
+    "@angular/platform-browser": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.3.tgz",
+      "integrity": "sha512-ceAPP2Ijmk2sZ1rnOU/WNlE3DtT6K6ljpjO9oUfXKMoSMdWirJKAraT3m/BAzmYwMSXpPBxA7c3paZjiLL6t5A==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/platform-browser-dynamic": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.3.tgz",
+      "integrity": "sha512-ZjQjSYslSQAKzM4llvyMFxnSjFpbhT1U9FOdKwscPe475zAKX0087qsHrP2CRwkJRfwtdcmj9wMUQIPlzMpHLA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/router": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.3.tgz",
+      "integrity": "sha512-CU5pLTfQVUnTN93mdIKJrVjXiNldUkk30DPz4lpdxpZjYOqFGXeeSeQWmToHSofLPodNcAB4kkZ41VyXvlBu7w==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@auth0/angular-jwt": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-4.0.0.tgz",
+      "integrity": "sha512-CHvk1zJ9jpQupl0f5y7EmTvYAwugyFvC4ztLsZKr7ZC7anNVaDd1+pDFJYS+ZEU9jLWzE74+AfVKfigImADJuw==",
+      "requires": {
+        "url": "^0.11.0"
+      }
+    },
+    "@babel/code-frame": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
+      "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
+      "requires": {
+        "@babel/highlight": "^7.8.3"
+      }
+    },
+    "@babel/compat-data": {
+      "version": "7.8.0",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.0.tgz",
+      "integrity": "sha512-ixPUWJpnd9hHvRkyIE3mJ6PY5DEWmR08UkcpdqI5kV5g/d6knT8Wth1LE5v5sVTIJkm9dGpQsXnhwxcf2/PjAg==",
+      "requires": {
+        "browserslist": "^4.8.2",
+        "invariant": "^2.2.4",
+        "semver": "^7.1.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "7.3.2",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+          "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
+        }
+      }
+    },
+    "@babel/core": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz",
+      "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==",
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@babel/generator": "^7.8.3",
+        "@babel/helpers": "^7.8.3",
+        "@babel/parser": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3",
+        "convert-source-map": "^1.7.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.1",
+        "json5": "^2.1.0",
+        "lodash": "^4.17.13",
+        "resolve": "^1.3.2",
+        "semver": "^5.4.1",
+        "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "json5": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+          "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+          "requires": {
+            "minimist": "^1.2.5"
+          }
+        },
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "@babel/generator": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz",
+      "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==",
+      "requires": {
+        "@babel/types": "^7.8.3",
+        "jsesc": "^2.5.1",
+        "lodash": "^4.17.13",
+        "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "2.5.2",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+          "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "@babel/helper-annotate-as-pure": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz",
+      "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-builder-binary-assignment-operator-visitor": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz",
+      "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==",
+      "requires": {
+        "@babel/helper-explode-assignable-expression": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-compilation-targets": {
+      "version": "7.8.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz",
+      "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==",
+      "requires": {
+        "browserslist": "^4.9.1",
+        "invariant": "^2.2.4",
+        "levenary": "^1.1.1",
+        "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "browserslist": {
+          "version": "4.12.0",
+          "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
+          "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
+          "requires": {
+            "caniuse-lite": "^1.0.30001043",
+            "electron-to-chromium": "^1.3.413",
+            "node-releases": "^1.1.53",
+            "pkg-up": "^2.0.0"
+          }
+        },
+        "caniuse-lite": {
+          "version": "1.0.30001045",
+          "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001045.tgz",
+          "integrity": "sha512-Y8o2Iz1KPcD6FjySbk1sPpvJqchgxk/iow0DABpGyzA1UeQAuxh63Xh0Enj5/BrsYbXtCN32JmR4ZxQTCQ6E6A=="
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
+    "@babel/helper-create-regexp-features-plugin": {
+      "version": "7.8.8",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz",
+      "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==",
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-regex": "^7.8.3",
+        "regexpu-core": "^4.7.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
+        },
+        "regexpu-core": {
+          "version": "4.7.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
+          "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
+          "requires": {
+            "regenerate": "^1.4.0",
+            "regenerate-unicode-properties": "^8.2.0",
+            "regjsgen": "^0.5.1",
+            "regjsparser": "^0.6.4",
+            "unicode-match-property-ecmascript": "^1.0.4",
+            "unicode-match-property-value-ecmascript": "^1.2.0"
+          }
+        },
+        "regjsgen": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz",
+          "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg=="
+        },
+        "regjsparser": {
+          "version": "0.6.4",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
+          "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
+          "requires": {
+            "jsesc": "~0.5.0"
+          }
+        }
+      }
+    },
+    "@babel/helper-define-map": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz",
+      "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==",
+      "requires": {
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/types": "^7.8.3",
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/helper-explode-assignable-expression": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz",
+      "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==",
+      "requires": {
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-function-name": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz",
+      "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==",
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-get-function-arity": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz",
+      "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-hoist-variables": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz",
+      "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-member-expression-to-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz",
+      "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-module-imports": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz",
+      "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-module-transforms": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz",
+      "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==",
+      "requires": {
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-replace-supers": "^7.8.6",
+        "@babel/helper-simple-access": "^7.8.3",
+        "@babel/helper-split-export-declaration": "^7.8.3",
+        "@babel/template": "^7.8.6",
+        "@babel/types": "^7.9.0",
+        "lodash": "^4.17.13"
+      },
+      "dependencies": {
+        "@babel/parser": {
+          "version": "7.9.4",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz",
+          "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA=="
+        },
+        "@babel/template": {
+          "version": "7.8.6",
+          "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
+          "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==",
+          "requires": {
+            "@babel/code-frame": "^7.8.3",
+            "@babel/parser": "^7.8.6",
+            "@babel/types": "^7.8.6"
+          }
+        },
+        "@babel/types": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
+          "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==",
+          "requires": {
+            "@babel/helper-validator-identifier": "^7.9.5",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        }
+      }
+    },
+    "@babel/helper-optimise-call-expression": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz",
+      "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-plugin-utils": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz",
+      "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ=="
+    },
+    "@babel/helper-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz",
+      "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==",
+      "requires": {
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/helper-remap-async-to-generator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz",
+      "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==",
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-wrap-function": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-replace-supers": {
+      "version": "7.8.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz",
+      "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==",
+      "requires": {
+        "@babel/helper-member-expression-to-functions": "^7.8.3",
+        "@babel/helper-optimise-call-expression": "^7.8.3",
+        "@babel/traverse": "^7.8.6",
+        "@babel/types": "^7.8.6"
+      },
+      "dependencies": {
+        "@babel/generator": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz",
+          "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==",
+          "requires": {
+            "@babel/types": "^7.9.5",
+            "jsesc": "^2.5.1",
+            "lodash": "^4.17.13",
+            "source-map": "^0.5.0"
+          }
+        },
+        "@babel/helper-function-name": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz",
+          "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==",
+          "requires": {
+            "@babel/helper-get-function-arity": "^7.8.3",
+            "@babel/template": "^7.8.3",
+            "@babel/types": "^7.9.5"
+          }
+        },
+        "@babel/parser": {
+          "version": "7.9.4",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz",
+          "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA=="
+        },
+        "@babel/traverse": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz",
+          "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==",
+          "requires": {
+            "@babel/code-frame": "^7.8.3",
+            "@babel/generator": "^7.9.5",
+            "@babel/helper-function-name": "^7.9.5",
+            "@babel/helper-split-export-declaration": "^7.8.3",
+            "@babel/parser": "^7.9.0",
+            "@babel/types": "^7.9.5",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          }
+        },
+        "@babel/types": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
+          "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==",
+          "requires": {
+            "@babel/helper-validator-identifier": "^7.9.5",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "@babel/helper-simple-access": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz",
+      "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==",
+      "requires": {
+        "@babel/template": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-split-export-declaration": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz",
+      "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==",
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-validator-identifier": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
+      "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g=="
+    },
+    "@babel/helper-wrap-function": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz",
+      "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==",
+      "requires": {
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helpers": {
+      "version": "7.9.2",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz",
+      "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==",
+      "requires": {
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.9.0",
+        "@babel/types": "^7.9.0"
+      },
+      "dependencies": {
+        "@babel/generator": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz",
+          "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==",
+          "requires": {
+            "@babel/types": "^7.9.5",
+            "jsesc": "^2.5.1",
+            "lodash": "^4.17.13",
+            "source-map": "^0.5.0"
+          }
+        },
+        "@babel/helper-function-name": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz",
+          "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==",
+          "requires": {
+            "@babel/helper-get-function-arity": "^7.8.3",
+            "@babel/template": "^7.8.3",
+            "@babel/types": "^7.9.5"
+          }
+        },
+        "@babel/parser": {
+          "version": "7.9.4",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz",
+          "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA=="
+        },
+        "@babel/traverse": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz",
+          "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==",
+          "requires": {
+            "@babel/code-frame": "^7.8.3",
+            "@babel/generator": "^7.9.5",
+            "@babel/helper-function-name": "^7.9.5",
+            "@babel/helper-split-export-declaration": "^7.8.3",
+            "@babel/parser": "^7.9.0",
+            "@babel/types": "^7.9.5",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          }
+        },
+        "@babel/types": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
+          "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==",
+          "requires": {
+            "@babel/helper-validator-identifier": "^7.9.5",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz",
+      "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==",
+      "requires": {
+        "chalk": "^2.0.0",
+        "esutils": "^2.0.2",
+        "js-tokens": "^4.0.0"
+      },
+      "dependencies": {
+        "js-tokens": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+          "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+        }
+      }
+    },
+    "@babel/parser": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz",
+      "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw=="
+    },
+    "@babel/plugin-proposal-async-generator-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz",
+      "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-remap-async-to-generator": "^7.8.3",
+        "@babel/plugin-syntax-async-generators": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-dynamic-import": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
+      "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-dynamic-import": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-json-strings": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz",
+      "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-json-strings": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-nullish-coalescing-operator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz",
+      "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-object-rest-spread": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz",
+      "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+        "@babel/plugin-transform-parameters": "^7.9.5"
+      }
+    },
+    "@babel/plugin-proposal-optional-catch-binding": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz",
+      "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-optional-chaining": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz",
+      "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-unicode-property-regex": {
+      "version": "7.8.8",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz",
+      "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==",
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.8",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-syntax-async-generators": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+      "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-dynamic-import": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+      "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-json-strings": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+      "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-nullish-coalescing-operator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+      "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-object-rest-spread": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+      "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-optional-catch-binding": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+      "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-optional-chaining": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+      "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-top-level-await": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz",
+      "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-arrow-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz",
+      "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-async-to-generator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz",
+      "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==",
+      "requires": {
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-remap-async-to-generator": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-block-scoped-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz",
+      "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-block-scoping": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz",
+      "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/plugin-transform-classes": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz",
+      "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==",
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-define-map": "^7.8.3",
+        "@babel/helper-function-name": "^7.9.5",
+        "@babel/helper-optimise-call-expression": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-replace-supers": "^7.8.6",
+        "@babel/helper-split-export-declaration": "^7.8.3",
+        "globals": "^11.1.0"
+      },
+      "dependencies": {
+        "@babel/helper-function-name": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz",
+          "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==",
+          "requires": {
+            "@babel/helper-get-function-arity": "^7.8.3",
+            "@babel/template": "^7.8.3",
+            "@babel/types": "^7.9.5"
+          }
+        },
+        "@babel/types": {
+          "version": "7.9.5",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
+          "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==",
+          "requires": {
+            "@babel/helper-validator-identifier": "^7.9.5",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        }
+      }
+    },
+    "@babel/plugin-transform-computed-properties": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz",
+      "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-destructuring": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz",
+      "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-dotall-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz",
+      "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==",
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-duplicate-keys": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz",
+      "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-exponentiation-operator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz",
+      "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==",
+      "requires": {
+        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-for-of": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz",
+      "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-function-name": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz",
+      "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==",
+      "requires": {
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz",
+      "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-member-expression-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz",
+      "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-modules-amd": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz",
+      "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==",
+      "requires": {
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-commonjs": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz",
+      "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==",
+      "requires": {
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-simple-access": "^7.8.3",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-systemjs": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz",
+      "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==",
+      "requires": {
+        "@babel/helper-hoist-variables": "^7.8.3",
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-umd": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz",
+      "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==",
+      "requires": {
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-named-capturing-groups-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz",
+      "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==",
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-new-target": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz",
+      "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-object-super": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz",
+      "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-replace-supers": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-parameters": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz",
+      "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==",
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-property-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz",
+      "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-regenerator": {
+      "version": "7.8.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz",
+      "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==",
+      "requires": {
+        "regenerator-transform": "^0.14.2"
+      }
+    },
+    "@babel/plugin-transform-reserved-words": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz",
+      "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-shorthand-properties": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz",
+      "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-spread": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz",
+      "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-sticky-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz",
+      "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-regex": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-template-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz",
+      "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==",
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-typeof-symbol": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz",
+      "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==",
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-unicode-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz",
+      "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==",
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/preset-env": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.3.tgz",
+      "integrity": "sha512-Rs4RPL2KjSLSE2mWAx5/iCH+GC1ikKdxPrhnRS6PfFVaiZeom22VFKN4X8ZthyN61kAaR05tfXTbCvatl9WIQg==",
+      "requires": {
+        "@babel/compat-data": "^7.8.0",
+        "@babel/helper-compilation-targets": "^7.8.3",
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
+        "@babel/plugin-proposal-dynamic-import": "^7.8.3",
+        "@babel/plugin-proposal-json-strings": "^7.8.3",
+        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
+        "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
+        "@babel/plugin-proposal-optional-catch-binding": "^7.8.3",
+        "@babel/plugin-proposal-optional-chaining": "^7.8.3",
+        "@babel/plugin-proposal-unicode-property-regex": "^7.8.3",
+        "@babel/plugin-syntax-async-generators": "^7.8.0",
+        "@babel/plugin-syntax-dynamic-import": "^7.8.0",
+        "@babel/plugin-syntax-json-strings": "^7.8.0",
+        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
+        "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
+        "@babel/plugin-syntax-optional-chaining": "^7.8.0",
+        "@babel/plugin-syntax-top-level-await": "^7.8.3",
+        "@babel/plugin-transform-arrow-functions": "^7.8.3",
+        "@babel/plugin-transform-async-to-generator": "^7.8.3",
+        "@babel/plugin-transform-block-scoped-functions": "^7.8.3",
+        "@babel/plugin-transform-block-scoping": "^7.8.3",
+        "@babel/plugin-transform-classes": "^7.8.3",
+        "@babel/plugin-transform-computed-properties": "^7.8.3",
+        "@babel/plugin-transform-destructuring": "^7.8.3",
+        "@babel/plugin-transform-dotall-regex": "^7.8.3",
+        "@babel/plugin-transform-duplicate-keys": "^7.8.3",
+        "@babel/plugin-transform-exponentiation-operator": "^7.8.3",
+        "@babel/plugin-transform-for-of": "^7.8.3",
+        "@babel/plugin-transform-function-name": "^7.8.3",
+        "@babel/plugin-transform-literals": "^7.8.3",
+        "@babel/plugin-transform-member-expression-literals": "^7.8.3",
+        "@babel/plugin-transform-modules-amd": "^7.8.3",
+        "@babel/plugin-transform-modules-commonjs": "^7.8.3",
+        "@babel/plugin-transform-modules-systemjs": "^7.8.3",
+        "@babel/plugin-transform-modules-umd": "^7.8.3",
+        "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
+        "@babel/plugin-transform-new-target": "^7.8.3",
+        "@babel/plugin-transform-object-super": "^7.8.3",
+        "@babel/plugin-transform-parameters": "^7.8.3",
+        "@babel/plugin-transform-property-literals": "^7.8.3",
+        "@babel/plugin-transform-regenerator": "^7.8.3",
+        "@babel/plugin-transform-reserved-words": "^7.8.3",
+        "@babel/plugin-transform-shorthand-properties": "^7.8.3",
+        "@babel/plugin-transform-spread": "^7.8.3",
+        "@babel/plugin-transform-sticky-regex": "^7.8.3",
+        "@babel/plugin-transform-template-literals": "^7.8.3",
+        "@babel/plugin-transform-typeof-symbol": "^7.8.3",
+        "@babel/plugin-transform-unicode-regex": "^7.8.3",
+        "@babel/types": "^7.8.3",
+        "browserslist": "^4.8.2",
+        "core-js-compat": "^3.6.2",
+        "invariant": "^2.2.2",
+        "levenary": "^1.1.0",
+        "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
+    "@babel/runtime": {
+      "version": "7.9.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
+      "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
+      "requires": {
+        "regenerator-runtime": "^0.13.4"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.13.5",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
+          "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
+        }
+      }
+    },
+    "@babel/template": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz",
+      "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==",
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@babel/parser": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/traverse": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz",
+      "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==",
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@babel/generator": "^7.8.4",
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/helper-split-export-declaration": "^7.8.3",
+        "@babel/parser": "^7.8.4",
+        "@babel/types": "^7.8.3",
+        "debug": "^4.1.0",
+        "globals": "^11.1.0",
+        "lodash": "^4.17.13"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "globals": {
+          "version": "11.12.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+          "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "@babel/types": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz",
+      "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==",
+      "requires": {
+        "esutils": "^2.0.2",
+        "lodash": "^4.17.13",
+        "to-fast-properties": "^2.0.0"
+      },
+      "dependencies": {
+        "to-fast-properties": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+          "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
+        }
+      }
+    },
+    "@fortawesome/fontawesome-free": {
+      "version": "5.13.0",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz",
+      "integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg=="
+    },
+    "@istanbuljs/schema": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
+      "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw=="
+    },
+    "@ngtools/webpack": {
+      "version": "8.3.24",
+      "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.24.tgz",
+      "integrity": "sha512-OpR7t/99qNOpADayCuM67agBVdYkdbFyEEcOLaDFYh3LsefHOSSxtAGv8M77e7dguvtaljHTiVkMxgcXFsZM0Q==",
+      "requires": {
+        "@angular-devkit/core": "8.3.24",
+        "enhanced-resolve": "4.1.0",
+        "rxjs": "6.4.0",
+        "tree-kill": "1.2.2",
+        "webpack-sources": "1.4.3"
+      },
+      "dependencies": {
+        "@angular-devkit/core": {
+          "version": "8.3.24",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.24.tgz",
+          "integrity": "sha512-xpT5yg+ddGDnifryBv2sRSYtq5F3iZIS+lN/K2AhhEa50B7Z+QaCVlEzoV/IfrGd6sLArdnKYwjLHFZ0LElUuw==",
+          "requires": {
+            "ajv": "6.10.2",
+            "fast-json-stable-stringify": "2.0.0",
+            "magic-string": "0.25.3",
+            "rxjs": "6.4.0",
+            "source-map": "0.7.3"
+          }
+        },
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "magic-string": {
+          "version": "0.25.3",
+          "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz",
+          "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==",
+          "requires": {
+            "sourcemap-codec": "^1.4.4"
+          }
+        }
+      }
+    },
+    "@schematics/angular": {
+      "version": "8.0.6",
+      "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.0.6.tgz",
+      "integrity": "sha512-F0/MrbvrJQJIjt0GwEkmf9PZUX0xQlCjlDcH6U7yBni0/+R5Gd5g3G0f12fsSa2iAwpwrLkKpiQluj29eFituQ==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.0.6",
+        "@angular-devkit/schematics": "8.0.6"
+      }
+    },
+    "@schematics/update": {
+      "version": "0.800.6",
+      "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.800.6.tgz",
+      "integrity": "sha512-vrzGIJtMiwLWl96+aJXMYrPgPtktLRpY8ZiNnlLm3pMDmeg08uButRh/pQGt02HuO/apTNJ5g0bmG8K5wS4I5A==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.0.6",
+        "@angular-devkit/schematics": "8.0.6",
+        "@yarnpkg/lockfile": "1.1.0",
+        "ini": "1.3.5",
+        "pacote": "9.5.0",
+        "rxjs": "6.4.0",
+        "semver": "6.0.0",
+        "semver-intersect": "1.4.0"
+      }
+    },
+    "@types/events": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+      "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
+    },
+    "@types/glob": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+      "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+      "requires": {
+        "@types/events": "*",
+        "@types/minimatch": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/jasmine": {
+      "version": "3.3.16",
+      "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.16.tgz",
+      "integrity": "sha512-Nveep4zKGby8uIvG2AEUyYOwZS8uVeHK9TgbuWYSawUDDdIgfhCKz28QzamTo//Jk7Ztt9PO3f+vzlB6a4GV1Q==",
+      "dev": true
+    },
+    "@types/jasminewd2": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.8.tgz",
+      "integrity": "sha512-d9p31r7Nxk0ZH0U39PTH0hiDlJ+qNVGjlt1ucOoTUptxb2v+Y5VMnsxfwN+i3hK4yQnqBi3FMmoMFcd1JHDxdg==",
+      "dev": true,
+      "requires": {
+        "@types/jasmine": "*"
+      }
+    },
+    "@types/minimatch": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+      "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
+    },
+    "@types/node": {
+      "version": "8.9.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
+      "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ=="
+    },
+    "@types/q": {
+      "version": "0.0.32",
+      "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
+      "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=",
+      "dev": true
+    },
+    "@types/selenium-webdriver": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz",
+      "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==",
+      "dev": true
+    },
+    "@types/source-list-map": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
+      "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA=="
+    },
+    "@types/webpack-sources": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz",
+      "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==",
+      "requires": {
+        "@types/node": "*",
+        "@types/source-list-map": "*",
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "@webassemblyjs/ast": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
+      "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==",
+      "requires": {
+        "@webassemblyjs/helper-module-context": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/wast-parser": "1.8.5"
+      }
+    },
+    "@webassemblyjs/floating-point-hex-parser": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz",
+      "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ=="
+    },
+    "@webassemblyjs/helper-api-error": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz",
+      "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA=="
+    },
+    "@webassemblyjs/helper-buffer": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz",
+      "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q=="
+    },
+    "@webassemblyjs/helper-code-frame": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz",
+      "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==",
+      "requires": {
+        "@webassemblyjs/wast-printer": "1.8.5"
+      }
+    },
+    "@webassemblyjs/helper-fsm": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz",
+      "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow=="
+    },
+    "@webassemblyjs/helper-module-context": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
+      "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "mamacro": "^0.0.3"
+      }
+    },
+    "@webassemblyjs/helper-wasm-bytecode": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz",
+      "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ=="
+    },
+    "@webassemblyjs/helper-wasm-section": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz",
+      "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-buffer": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/wasm-gen": "1.8.5"
+      }
+    },
+    "@webassemblyjs/ieee754": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz",
+      "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
+      "requires": {
+        "@xtuc/ieee754": "^1.2.0"
+      }
+    },
+    "@webassemblyjs/leb128": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz",
+      "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==",
+      "requires": {
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/utf8": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz",
+      "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw=="
+    },
+    "@webassemblyjs/wasm-edit": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz",
+      "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-buffer": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/helper-wasm-section": "1.8.5",
+        "@webassemblyjs/wasm-gen": "1.8.5",
+        "@webassemblyjs/wasm-opt": "1.8.5",
+        "@webassemblyjs/wasm-parser": "1.8.5",
+        "@webassemblyjs/wast-printer": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wasm-gen": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz",
+      "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/ieee754": "1.8.5",
+        "@webassemblyjs/leb128": "1.8.5",
+        "@webassemblyjs/utf8": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wasm-opt": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz",
+      "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-buffer": "1.8.5",
+        "@webassemblyjs/wasm-gen": "1.8.5",
+        "@webassemblyjs/wasm-parser": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wasm-parser": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz",
+      "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-api-error": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/ieee754": "1.8.5",
+        "@webassemblyjs/leb128": "1.8.5",
+        "@webassemblyjs/utf8": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wast-parser": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz",
+      "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/floating-point-hex-parser": "1.8.5",
+        "@webassemblyjs/helper-api-error": "1.8.5",
+        "@webassemblyjs/helper-code-frame": "1.8.5",
+        "@webassemblyjs/helper-fsm": "1.8.5",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/wast-printer": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz",
+      "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/wast-parser": "1.8.5",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@xtuc/ieee754": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
+    },
+    "@xtuc/long": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
+    },
+    "@yarnpkg/lockfile": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+      "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+      "dev": true
+    },
+    "JSONStream": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+      "dev": true,
+      "requires": {
+        "jsonparse": "^1.2.0",
+        "through": ">=2.2.7 <3"
+      }
+    },
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "acorn": {
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+      "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA=="
+    },
+    "adler-32": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz",
+      "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
+      "requires": {
+        "exit-on-epipe": "~1.0.1",
+        "printj": "~1.1.0"
+      }
+    },
+    "adm-zip": {
+      "version": "0.4.14",
+      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz",
+      "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==",
+      "dev": true
+    },
+    "after": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+      "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
+      "dev": true
+    },
+    "agent-base": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+      "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+      "dev": true,
+      "requires": {
+        "es6-promisify": "^5.0.0"
+      }
+    },
+    "agentkeepalive": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
+      "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
+      "dev": true,
+      "requires": {
+        "humanize-ms": "^1.2.1"
+      }
+    },
+    "ajv": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+      "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
+      "requires": {
+        "fast-deep-equal": "^2.0.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ajv-errors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+      "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ=="
+    },
+    "ajv-keywords": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
+      "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ=="
+    },
+    "amdefine": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+    },
+    "ansi-colors": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
+      "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA=="
+    },
+    "ansi-escapes": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+      "dev": true
+    },
+    "ansi-html": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
+      "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4="
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "anymatch": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+      "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+      "requires": {
+        "micromatch": "^3.1.4",
+        "normalize-path": "^2.1.1"
+      },
+      "dependencies": {
+        "normalize-path": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+          "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+          "requires": {
+            "remove-trailing-separator": "^1.0.1"
+          }
+        }
+      }
+    },
+    "app-root-path": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz",
+      "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==",
+      "dev": true
+    },
+    "append-transform": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
+      "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==",
+      "dev": true,
+      "requires": {
+        "default-require-extensions": "^2.0.0"
+      }
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+    },
+    "arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "aria-query": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
+      "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
+      "dev": true,
+      "requires": {
+        "ast-types-flow": "0.0.7",
+        "commander": "^2.11.0"
+      }
+    },
+    "arr-diff": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
+    },
+    "arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
+    },
+    "array-flatten": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
+      "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ=="
+    },
+    "array-union": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "requires": {
+        "array-uniq": "^1.0.1"
+      }
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
+    },
+    "array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+    },
+    "arraybuffer.slice": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
+      "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
+      "dev": true
+    },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
+    },
+    "asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "asn1.js": {
+      "version": "4.10.1",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+      "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+      "requires": {
+        "object-assign": "^4.1.1",
+        "util": "0.10.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        }
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
+    "assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
+    },
+    "ast-types-flow": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+      "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
+      "dev": true
+    },
+    "async": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+      "requires": {
+        "lodash": "^4.17.14"
+      }
+    },
+    "async-each": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+      "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ=="
+    },
+    "async-limiter": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+    },
+    "autoprefixer": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz",
+      "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==",
+      "requires": {
+        "browserslist": "^4.6.3",
+        "caniuse-lite": "^1.0.30000980",
+        "chalk": "^2.4.2",
+        "normalize-range": "^0.1.2",
+        "num2fraction": "^1.2.2",
+        "postcss": "^7.0.17",
+        "postcss-value-parser": "^4.0.0"
+      }
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+    },
+    "aws4": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+      "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+    },
+    "axobject-query": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
+      "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
+      "dev": true,
+      "requires": {
+        "ast-types-flow": "0.0.7"
+      }
+    },
+    "babel-code-frame": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+      "dev": true,
+      "requires": {
+        "chalk": "^1.1.3",
+        "esutils": "^2.0.2",
+        "js-tokens": "^3.0.2"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "babel-plugin-dynamic-import-node": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
+      "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
+      "requires": {
+        "object.assign": "^4.1.0"
+      }
+    },
+    "backo2": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+      "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+      "requires": {
+        "cache-base": "^1.0.1",
+        "class-utils": "^0.3.5",
+        "component-emitter": "^1.2.1",
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.1",
+        "mixin-deep": "^1.2.0",
+        "pascalcase": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "base64-arraybuffer": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+      "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
+      "dev": true
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
+    },
+    "base64id": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
+      "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
+      "dev": true
+    },
+    "batch": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+      "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY="
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "better-assert": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+      "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+      "dev": true,
+      "requires": {
+        "callsite": "1.0.0"
+      }
+    },
+    "big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
+    },
+    "binary-extensions": {
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+      "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
+    },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "optional": true,
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
+    "blob": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
+      "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
+      "dev": true
+    },
+    "blocking-proxy": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz",
+      "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
+    "bluebird": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
+    },
+    "bn.js": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+    },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      },
+      "dependencies": {
+        "bytes": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+          "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+        },
+        "qs": {
+          "version": "6.7.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+          "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+        }
+      }
+    },
+    "bonjour": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
+      "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+      "requires": {
+        "array-flatten": "^2.1.0",
+        "deep-equal": "^1.0.1",
+        "dns-equal": "^1.0.0",
+        "dns-txt": "^2.0.2",
+        "multicast-dns": "^6.0.1",
+        "multicast-dns-service-types": "^1.1.0"
+      }
+    },
+    "bootstrap": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz",
+      "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA=="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+      "requires": {
+        "arr-flatten": "^1.1.0",
+        "array-unique": "^0.3.2",
+        "extend-shallow": "^2.0.1",
+        "fill-range": "^4.0.0",
+        "isobject": "^3.0.1",
+        "repeat-element": "^1.1.2",
+        "snapdragon": "^0.8.1",
+        "snapdragon-node": "^2.0.1",
+        "split-string": "^3.0.2",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "browserify-cipher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "requires": {
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
+      }
+    },
+    "browserify-des": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "browserify-rsa": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "randombytes": "^2.0.1"
+      }
+    },
+    "browserify-sign": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+      "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+      "requires": {
+        "bn.js": "^4.1.1",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.2",
+        "elliptic": "^6.0.0",
+        "inherits": "^2.0.1",
+        "parse-asn1": "^5.0.0"
+      }
+    },
+    "browserify-zlib": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "requires": {
+        "pako": "~1.0.5"
+      }
+    },
+    "browserslist": {
+      "version": "4.8.3",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.3.tgz",
+      "integrity": "sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg==",
+      "requires": {
+        "caniuse-lite": "^1.0.30001017",
+        "electron-to-chromium": "^1.3.322",
+        "node-releases": "^1.1.44"
+      }
+    },
+    "browserstack": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz",
+      "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==",
+      "dev": true,
+      "requires": {
+        "https-proxy-agent": "^2.2.1"
+      }
+    },
+    "buffer": {
+      "version": "4.9.2",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+      "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4",
+        "isarray": "^1.0.0"
+      }
+    },
+    "buffer-alloc": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+      "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+      "dev": true,
+      "requires": {
+        "buffer-alloc-unsafe": "^1.1.0",
+        "buffer-fill": "^1.0.0"
+      }
+    },
+    "buffer-alloc-unsafe": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+      "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+      "dev": true
+    },
+    "buffer-fill": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+      "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+      "dev": true
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+    },
+    "buffer-indexof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
+      "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g=="
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+    },
+    "builtin-modules": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+      "dev": true
+    },
+    "builtin-status-codes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
+    },
+    "builtins": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+      "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
+      "dev": true
+    },
+    "bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+    },
+    "cacache": {
+      "version": "11.3.3",
+      "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz",
+      "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.5",
+        "chownr": "^1.1.1",
+        "figgy-pudding": "^3.5.1",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.1.15",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "move-concurrently": "^1.0.1",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^2.6.3",
+        "ssri": "^6.0.1",
+        "unique-filename": "^1.1.1",
+        "y18n": "^4.0.0"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
+    "cache-base": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+      "requires": {
+        "collection-visit": "^1.0.0",
+        "component-emitter": "^1.2.1",
+        "get-value": "^2.0.6",
+        "has-value": "^1.0.0",
+        "isobject": "^3.0.1",
+        "set-value": "^2.0.0",
+        "to-object-path": "^0.3.0",
+        "union-value": "^1.0.0",
+        "unset-value": "^1.0.0"
+      }
+    },
+    "caller-callsite": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+      "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+      "requires": {
+        "callsites": "^2.0.0"
+      }
+    },
+    "caller-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+      "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+      "requires": {
+        "caller-callsite": "^2.0.0"
+      }
+    },
+    "callsite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+      "dev": true
+    },
+    "callsites": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+      "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+    },
+    "caniuse-lite": {
+      "version": "1.0.30001019",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz",
+      "integrity": "sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g=="
+    },
+    "canonical-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz",
+      "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "cdk-table-exporter": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/cdk-table-exporter/-/cdk-table-exporter-9.0.0.tgz",
+      "integrity": "sha512-F//RrIX9ek5wwYFAG2VzgtL3l48tRPw7SLb0VpXobqYOxjcZBQlha+eFsDDhYfBkz/PHxSNNY3rPZen2O9ynVw==",
+      "requires": {
+        "file-saver": "^2.0.2",
+        "xlsx": "^0.14.1"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.17.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
+        },
+        "xlsx": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.14.5.tgz",
+          "integrity": "sha512-s/5f4/mjeWREmIWZ+HtDfh/rnz51ar+dZ4LWKZU3u9VBx2zLdSIWTdXgoa52/pnZ9Oe/Vu1W1qzcKzLVe+lq4w==",
+          "requires": {
+            "adler-32": "~1.2.0",
+            "cfb": "^1.1.2",
+            "codepage": "~1.14.0",
+            "commander": "~2.17.1",
+            "crc-32": "~1.2.0",
+            "exit-on-epipe": "~1.0.1",
+            "ssf": "~0.10.2"
+          }
+        }
+      }
+    },
+    "cfb": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.1.4.tgz",
+      "integrity": "sha512-rwFkl3aFO3f+ljR27YINwC0x8vPjyiEVbYbrTCKzspEf7Q++3THdfHVgJYNUbxNcupJECrLX+L40Mjm9hm/Bgw==",
+      "requires": {
+        "adler-32": "~1.2.0",
+        "commander": "^2.16.0",
+        "crc-32": "~1.2.0",
+        "printj": "~1.1.2"
+      }
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "dependencies": {
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "chokidar": {
+      "version": "2.1.8",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+      "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+      "requires": {
+        "anymatch": "^2.0.0",
+        "async-each": "^1.0.1",
+        "braces": "^2.3.2",
+        "fsevents": "^1.2.7",
+        "glob-parent": "^3.1.0",
+        "inherits": "^2.0.3",
+        "is-binary-path": "^1.0.0",
+        "is-glob": "^4.0.0",
+        "normalize-path": "^3.0.0",
+        "path-is-absolute": "^1.0.0",
+        "readdirp": "^2.2.1",
+        "upath": "^1.1.1"
+      }
+    },
+    "chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+    },
+    "chrome-trace-event": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+      "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "circular-dependency-plugin": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz",
+      "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw=="
+    },
+    "class-utils": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+      "requires": {
+        "arr-union": "^3.1.0",
+        "define-property": "^0.2.5",
+        "isobject": "^3.0.0",
+        "static-extend": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "clean-css": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
+      "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
+      "requires": {
+        "source-map": "~0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "cli-cursor": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+      "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^2.0.0"
+      }
+    },
+    "cli-width": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+      "dev": true
+    },
+    "cliui": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+      "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+      "requires": {
+        "string-width": "^2.1.1",
+        "strip-ansi": "^4.0.0",
+        "wrap-ansi": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
+    },
+    "clone-deep": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+      "requires": {
+        "is-plain-object": "^2.0.4",
+        "kind-of": "^6.0.2",
+        "shallow-clone": "^3.0.0"
+      }
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+    },
+    "codelyzer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.1.tgz",
+      "integrity": "sha512-awBZXFcJUyC5HMYXiHzjr3D24tww2l1D1OqtfA9vUhEtYr32a65A+Gblm/OvsO+HuKLYzn8EDMw1inSM3VbxWA==",
+      "dev": true,
+      "requires": {
+        "app-root-path": "^2.2.1",
+        "aria-query": "^3.0.0",
+        "axobject-query": "2.0.2",
+        "css-selector-tokenizer": "^0.7.1",
+        "cssauron": "^1.4.0",
+        "damerau-levenshtein": "^1.0.4",
+        "semver-dsl": "^1.0.1",
+        "source-map": "^0.5.7",
+        "sprintf-js": "^1.1.2"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        },
+        "sprintf-js": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+          "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+          "dev": true
+        }
+      }
+    },
+    "codepage": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz",
+      "integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=",
+      "requires": {
+        "commander": "~2.14.1",
+        "exit-on-epipe": "~1.0.1"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.14.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
+          "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw=="
+        }
+      }
+    },
+    "collection-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+      "requires": {
+        "map-visit": "^1.0.0",
+        "object-visit": "^1.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+    },
+    "colors": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+      "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
+      "dev": true
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+    },
+    "commondir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
+    },
+    "compare-versions": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
+      "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
+      "dev": true
+    },
+    "component-bind": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+      "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
+      "dev": true
+    },
+    "component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
+    },
+    "component-inherit": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+      "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
+      "dev": true
+    },
+    "compressible": {
+      "version": "2.0.18",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+      "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+      "requires": {
+        "mime-db": ">= 1.43.0 < 2"
+      }
+    },
+    "compression": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+      "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+      "requires": {
+        "accepts": "~1.3.5",
+        "bytes": "3.0.0",
+        "compressible": "~2.0.16",
+        "debug": "2.6.9",
+        "on-headers": "~1.0.2",
+        "safe-buffer": "5.1.2",
+        "vary": "~1.1.2"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "connect": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+      "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "finalhandler": "1.1.2",
+        "parseurl": "~1.3.3",
+        "utils-merge": "1.0.1"
+      }
+    },
+    "connect-history-api-fallback": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
+      "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg=="
+    },
+    "console-browserify": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA=="
+    },
+    "constants-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "convert-source-map": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+      "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+      "requires": {
+        "safe-buffer": "~5.1.1"
+      }
+    },
+    "cookie": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "copy-concurrently": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+      "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+      "requires": {
+        "aproba": "^1.1.1",
+        "fs-write-stream-atomic": "^1.0.8",
+        "iferr": "^0.1.5",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.0"
+      }
+    },
+    "copy-descriptor": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
+    },
+    "copy-webpack-plugin": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz",
+      "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==",
+      "requires": {
+        "cacache": "^12.0.3",
+        "find-cache-dir": "^2.1.0",
+        "glob-parent": "^3.1.0",
+        "globby": "^7.1.1",
+        "is-glob": "^4.0.1",
+        "loader-utils": "^1.2.3",
+        "minimatch": "^3.0.4",
+        "normalize-path": "^3.0.0",
+        "p-limit": "^2.2.1",
+        "schema-utils": "^1.0.0",
+        "serialize-javascript": "^2.1.2",
+        "webpack-log": "^2.0.0"
+      },
+      "dependencies": {
+        "cacache": {
+          "version": "12.0.4",
+          "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+          "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+          "requires": {
+            "bluebird": "^3.5.5",
+            "chownr": "^1.1.1",
+            "figgy-pudding": "^3.5.1",
+            "glob": "^7.1.4",
+            "graceful-fs": "^4.1.15",
+            "infer-owner": "^1.0.3",
+            "lru-cache": "^5.1.1",
+            "mississippi": "^3.0.0",
+            "mkdirp": "^0.5.1",
+            "move-concurrently": "^1.0.1",
+            "promise-inflight": "^1.0.1",
+            "rimraf": "^2.6.3",
+            "ssri": "^6.0.1",
+            "unique-filename": "^1.1.1",
+            "y18n": "^4.0.0"
+          }
+        },
+        "find-cache-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+          "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+          "requires": {
+            "commondir": "^1.0.1",
+            "make-dir": "^2.0.0",
+            "pkg-dir": "^3.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
+    "core-js": {
+      "version": "3.6.4",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
+      "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
+    },
+    "core-js-compat": {
+      "version": "3.6.5",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
+      "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==",
+      "requires": {
+        "browserslist": "^4.8.5",
+        "semver": "7.0.0"
+      },
+      "dependencies": {
+        "browserslist": {
+          "version": "4.12.0",
+          "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
+          "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
+          "requires": {
+            "caniuse-lite": "^1.0.30001043",
+            "electron-to-chromium": "^1.3.413",
+            "node-releases": "^1.1.53",
+            "pkg-up": "^2.0.0"
+          }
+        },
+        "caniuse-lite": {
+          "version": "1.0.30001045",
+          "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001045.tgz",
+          "integrity": "sha512-Y8o2Iz1KPcD6FjySbk1sPpvJqchgxk/iow0DABpGyzA1UeQAuxh63Xh0Enj5/BrsYbXtCN32JmR4ZxQTCQ6E6A=="
+        },
+        "semver": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+          "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A=="
+        }
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "cosmiconfig": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+      "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+      "requires": {
+        "import-fresh": "^2.0.0",
+        "is-directory": "^0.3.1",
+        "js-yaml": "^3.13.1",
+        "parse-json": "^4.0.0"
+      }
+    },
+    "coverage-istanbul-loader": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/coverage-istanbul-loader/-/coverage-istanbul-loader-2.0.3.tgz",
+      "integrity": "sha512-LiGRvyIuzVYs3M1ZYK1tF0HekjH0DJ8zFdUwAZq378EJzqOgToyb1690dp3TAUlP6Y+82uu42LRjuROVeJ54CA==",
+      "requires": {
+        "convert-source-map": "^1.7.0",
+        "istanbul-lib-instrument": "^4.0.0",
+        "loader-utils": "^1.2.3",
+        "merge-source-map": "^1.1.0",
+        "schema-utils": "^2.6.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+          "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+          "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+        },
+        "schema-utils": {
+          "version": "2.6.6",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
+          "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
+          "requires": {
+            "ajv": "^6.12.0",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "crc-32": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
+      "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
+      "requires": {
+        "exit-on-epipe": "~1.0.1",
+        "printj": "~1.1.0"
+      }
+    },
+    "create-ecdh": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+      "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.0.0"
+      }
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
+    "crypto-browserify": {
+      "version": "3.12.0",
+      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "requires": {
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
+      }
+    },
+    "css-parse": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
+      "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs="
+    },
+    "css-selector-tokenizer": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz",
+      "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==",
+      "dev": true,
+      "requires": {
+        "cssesc": "^0.1.0",
+        "fastparse": "^1.1.1",
+        "regexpu-core": "^1.0.0"
+      }
+    },
+    "cssauron": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
+      "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
+      "dev": true,
+      "requires": {
+        "through": "X.X.X"
+      }
+    },
+    "cssesc": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+      "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
+      "dev": true
+    },
+    "custom-event": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
+      "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=",
+      "dev": true
+    },
+    "cyclist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+      "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk="
+    },
+    "damerau-levenshtein": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
+      "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==",
+      "dev": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "date-format": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
+      "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "debuglog": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
+      "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
+      "dev": true
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+    },
+    "decode-uri-component": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+    },
+    "deep-equal": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+      "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+      "requires": {
+        "is-arguments": "^1.0.4",
+        "is-date-object": "^1.0.1",
+        "is-regex": "^1.0.4",
+        "object-is": "^1.0.1",
+        "object-keys": "^1.1.1",
+        "regexp.prototype.flags": "^1.2.0"
+      }
+    },
+    "default-gateway": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
+      "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==",
+      "requires": {
+        "execa": "^1.0.0",
+        "ip-regex": "^2.1.0"
+      }
+    },
+    "default-require-extensions": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
+      "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=",
+      "dev": true,
+      "requires": {
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "define-property": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "requires": {
+        "is-descriptor": "^1.0.2",
+        "isobject": "^3.0.1"
+      },
+      "dependencies": {
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "del": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
+      "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
+      "requires": {
+        "@types/glob": "^7.1.1",
+        "globby": "^6.1.0",
+        "is-path-cwd": "^2.0.0",
+        "is-path-in-cwd": "^2.0.0",
+        "p-map": "^2.0.0",
+        "pify": "^4.0.1",
+        "rimraf": "^2.6.3"
+      },
+      "dependencies": {
+        "globby": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+          "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+          "requires": {
+            "array-union": "^1.0.1",
+            "glob": "^7.0.3",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          },
+          "dependencies": {
+            "pify": {
+              "version": "2.3.0",
+              "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+              "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+            }
+          }
+        }
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "dependency-graph": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz",
+      "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==",
+      "dev": true
+    },
+    "des.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+      "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "detect-node": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+      "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw=="
+    },
+    "dezalgo": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+      "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
+      "dev": true,
+      "requires": {
+        "asap": "^2.0.0",
+        "wrappy": "1"
+      }
+    },
+    "di": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
+      "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
+      "dev": true
+    },
+    "diff": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "dev": true
+    },
+    "diffie-hellman": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
+      }
+    },
+    "dir-glob": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
+      "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
+      "requires": {
+        "path-type": "^3.0.0"
+      }
+    },
+    "dns-equal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
+      "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0="
+    },
+    "dns-packet": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
+      "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
+      "requires": {
+        "ip": "^1.1.0",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "dns-txt": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
+      "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+      "requires": {
+        "buffer-indexof": "^1.0.0"
+      }
+    },
+    "dom-serialize": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
+      "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
+      "dev": true,
+      "requires": {
+        "custom-event": "~1.0.0",
+        "ent": "~2.2.0",
+        "extend": "^3.0.0",
+        "void-elements": "^2.0.0"
+      }
+    },
+    "domain-browser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA=="
+    },
+    "dotenv": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+      "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
+      "dev": true
+    },
+    "duplexify": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+      "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+      "requires": {
+        "end-of-stream": "^1.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "electron-to-chromium": {
+      "version": "1.3.414",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.414.tgz",
+      "integrity": "sha512-UfxhIvED++qLwWrAq9uYVcqF8FdeV9sU2S7qhiHYFODxzXRrd1GZRl/PjITHsTEejgibcWDraD8TQqoHb1aCBQ=="
+    },
+    "elliptic": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
+      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      }
+    },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "emojis-list": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+      "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "encoding": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "dev": true,
+      "requires": {
+        "iconv-lite": "~0.4.13"
+      }
+    },
+    "end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "requires": {
+        "once": "^1.4.0"
+      }
+    },
+    "engine.io": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz",
+      "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.4",
+        "base64id": "1.0.0",
+        "cookie": "0.3.1",
+        "debug": "~3.1.0",
+        "engine.io-parser": "~2.1.0",
+        "ws": "~3.3.1"
+      },
+      "dependencies": {
+        "cookie": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+          "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+          "dev": true
+        },
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "engine.io-client": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
+      "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
+      "dev": true,
+      "requires": {
+        "component-emitter": "1.2.1",
+        "component-inherit": "0.0.3",
+        "debug": "~3.1.0",
+        "engine.io-parser": "~2.1.1",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "ws": "~3.3.1",
+        "xmlhttprequest-ssl": "~1.5.4",
+        "yeast": "0.1.2"
+      },
+      "dependencies": {
+        "component-emitter": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+          "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+          "dev": true
+        },
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "engine.io-parser": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
+      "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
+      "dev": true,
+      "requires": {
+        "after": "0.8.2",
+        "arraybuffer.slice": "~0.0.7",
+        "base64-arraybuffer": "0.1.5",
+        "blob": "0.0.5",
+        "has-binary2": "~1.0.2"
+      }
+    },
+    "enhanced-resolve": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
+      "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "memory-fs": "^0.4.0",
+        "tapable": "^1.0.0"
+      }
+    },
+    "ent": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+      "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
+      "dev": true
+    },
+    "err-code": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
+      "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=",
+      "dev": true
+    },
+    "errno": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "requires": {
+        "prr": "~1.0.1"
+      }
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.17.5",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
+      "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
+      "requires": {
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1",
+        "is-callable": "^1.1.5",
+        "is-regex": "^1.0.5",
+        "object-inspect": "^1.7.0",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.0",
+        "string.prototype.trimleft": "^2.1.1",
+        "string.prototype.trimright": "^2.1.1"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "dev": true
+    },
+    "es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+      "dev": true,
+      "requires": {
+        "es6-promise": "^4.0.3"
+      }
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+    },
+    "eslint-scope": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+      "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+      "requires": {
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+    },
+    "esrecurse": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+      "requires": {
+        "estraverse": "^4.1.0"
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "eventemitter3": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
+      "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg=="
+    },
+    "events": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
+      "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg=="
+    },
+    "eventsource": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
+      "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
+      "requires": {
+        "original": "^1.0.0"
+      }
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "execa": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+      "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+      "requires": {
+        "cross-spawn": "^6.0.0",
+        "get-stream": "^4.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
+      }
+    },
+    "exit": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+      "dev": true
+    },
+    "exit-on-epipe": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
+      "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw=="
+    },
+    "expand-brackets": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+      "requires": {
+        "debug": "^2.3.3",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "posix-character-classes": "^0.1.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "array-flatten": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+          "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+        },
+        "qs": {
+          "version": "6.7.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+          "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+        }
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "requires": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      }
+    },
+    "extglob": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+      "requires": {
+        "array-unique": "^0.3.2",
+        "define-property": "^1.0.0",
+        "expand-brackets": "^2.1.4",
+        "extend-shallow": "^2.0.1",
+        "fragment-cache": "^0.2.1",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+    },
+    "fast-deep-equal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+    },
+    "fastparse": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+      "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
+      "dev": true
+    },
+    "faye-websocket": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+      "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+      "requires": {
+        "websocket-driver": ">=0.5.1"
+      }
+    },
+    "figgy-pudding": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+      "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w=="
+    },
+    "figures": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+      "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "file-loader": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.2.0.tgz",
+      "integrity": "sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==",
+      "requires": {
+        "loader-utils": "^1.2.3",
+        "schema-utils": "^2.0.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+          "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+          "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+        },
+        "schema-utils": {
+          "version": "2.6.6",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
+          "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
+          "requires": {
+            "ajv": "^6.12.0",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "file-saver": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
+      "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
+    },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "optional": true
+    },
+    "fileset": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
+      "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=",
+      "dev": true,
+      "requires": {
+        "glob": "^7.0.3",
+        "minimatch": "^3.0.3"
+      }
+    },
+    "fill-range": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1",
+        "to-regex-range": "^2.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "find-cache-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz",
+      "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==",
+      "requires": {
+        "commondir": "^1.0.1",
+        "make-dir": "^3.0.0",
+        "pkg-dir": "^4.1.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "make-dir": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+          "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+          "requires": {
+            "semver": "^6.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+        },
+        "pkg-dir": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+          "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+          "requires": {
+            "find-up": "^4.0.0"
+          }
+        }
+      }
+    },
+    "find-up": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+      "requires": {
+        "locate-path": "^3.0.0"
+      }
+    },
+    "flatted": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+      "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+      "dev": true
+    },
+    "flush-write-stream": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+      "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.3.6"
+      }
+    },
+    "follow-redirects": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz",
+      "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==",
+      "requires": {
+        "debug": "^3.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "font-awesome": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+      "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "frac": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="
+    },
+    "fragment-cache": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+      "requires": {
+        "map-cache": "^0.2.2"
+      }
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "from2": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
+      }
+    },
+    "fs-access": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
+      "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=",
+      "dev": true,
+      "requires": {
+        "null-check": "^1.0.0"
+      }
+    },
+    "fs-extra": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+      "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
+    "fs-minipass": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
+      "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
+      "dev": true,
+      "requires": {
+        "minipass": "^2.6.0"
+      }
+    },
+    "fs-write-stream-atomic": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+      "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "iferr": "^0.1.5",
+        "imurmurhash": "^0.1.4",
+        "readable-stream": "1 || 2"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "fsevents": {
+      "version": "1.2.11",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz",
+      "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
+      "optional": true,
+      "requires": {
+        "bindings": "^1.5.0",
+        "nan": "^2.12.1",
+        "node-pre-gyp": "*"
+      },
+      "dependencies": {
+        "abbrev": {
+          "version": "1.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "ansi-regex": {
+          "version": "2.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "aproba": {
+          "version": "1.2.0",
+          "bundled": true,
+          "optional": true
+        },
+        "are-we-there-yet": {
+          "version": "1.1.5",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "delegates": "^1.0.0",
+            "readable-stream": "^2.0.6"
+          }
+        },
+        "balanced-match": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "brace-expansion": {
+          "version": "1.1.11",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "balanced-match": "^1.0.0",
+            "concat-map": "0.0.1"
+          }
+        },
+        "chownr": {
+          "version": "1.1.3",
+          "bundled": true,
+          "optional": true
+        },
+        "code-point-at": {
+          "version": "1.1.0",
+          "bundled": true,
+          "optional": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "console-control-strings": {
+          "version": "1.1.0",
+          "bundled": true,
+          "optional": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "debug": {
+          "version": "3.2.6",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "deep-extend": {
+          "version": "0.6.0",
+          "bundled": true,
+          "optional": true
+        },
+        "delegates": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "detect-libc": {
+          "version": "1.0.3",
+          "bundled": true,
+          "optional": true
+        },
+        "fs-minipass": {
+          "version": "1.2.7",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "minipass": "^2.6.0"
+          }
+        },
+        "fs.realpath": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "gauge": {
+          "version": "2.7.4",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "aproba": "^1.0.3",
+            "console-control-strings": "^1.0.0",
+            "has-unicode": "^2.0.0",
+            "object-assign": "^4.1.0",
+            "signal-exit": "^3.0.0",
+            "string-width": "^1.0.1",
+            "strip-ansi": "^3.0.1",
+            "wide-align": "^1.1.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.6",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "has-unicode": {
+          "version": "2.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "iconv-lite": {
+          "version": "0.4.24",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "safer-buffer": ">= 2.1.2 < 3"
+          }
+        },
+        "ignore-walk": {
+          "version": "3.0.3",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "minimatch": "^3.0.4"
+          }
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "once": "^1.3.0",
+            "wrappy": "1"
+          }
+        },
+        "inherits": {
+          "version": "2.0.4",
+          "bundled": true,
+          "optional": true
+        },
+        "ini": {
+          "version": "1.3.5",
+          "bundled": true,
+          "optional": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "minimatch": {
+          "version": "3.0.4",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "brace-expansion": "^1.1.7"
+          }
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "bundled": true,
+          "optional": true
+        },
+        "minipass": {
+          "version": "2.9.0",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.0"
+          }
+        },
+        "minizlib": {
+          "version": "1.3.3",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "minipass": "^2.9.0"
+          }
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "minimist": "0.0.8"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "bundled": true,
+          "optional": true
+        },
+        "needle": {
+          "version": "2.4.0",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "debug": "^3.2.6",
+            "iconv-lite": "^0.4.4",
+            "sax": "^1.2.4"
+          }
+        },
+        "node-pre-gyp": {
+          "version": "0.14.0",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "detect-libc": "^1.0.2",
+            "mkdirp": "^0.5.1",
+            "needle": "^2.2.1",
+            "nopt": "^4.0.1",
+            "npm-packlist": "^1.1.6",
+            "npmlog": "^4.0.2",
+            "rc": "^1.2.7",
+            "rimraf": "^2.6.1",
+            "semver": "^5.3.0",
+            "tar": "^4.4.2"
+          }
+        },
+        "nopt": {
+          "version": "4.0.1",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "abbrev": "1",
+            "osenv": "^0.1.4"
+          }
+        },
+        "npm-bundled": {
+          "version": "1.1.1",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "npm-normalize-package-bin": "^1.0.1"
+          }
+        },
+        "npm-normalize-package-bin": {
+          "version": "1.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "npm-packlist": {
+          "version": "1.4.7",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "ignore-walk": "^3.0.1",
+            "npm-bundled": "^1.0.1"
+          }
+        },
+        "npmlog": {
+          "version": "4.1.2",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "are-we-there-yet": "~1.1.2",
+            "console-control-strings": "~1.1.0",
+            "gauge": "~2.7.3",
+            "set-blocking": "~2.0.0"
+          }
+        },
+        "number-is-nan": {
+          "version": "1.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "wrappy": "1"
+          }
+        },
+        "os-homedir": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "osenv": {
+          "version": "0.1.5",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "os-homedir": "^1.0.0",
+            "os-tmpdir": "^1.0.0"
+          }
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "process-nextick-args": {
+          "version": "2.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "rc": {
+          "version": "1.2.8",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "deep-extend": "^0.6.0",
+            "ini": "~1.3.0",
+            "minimist": "^1.2.0",
+            "strip-json-comments": "~2.0.1"
+          },
+          "dependencies": {
+            "minimist": {
+              "version": "1.2.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
+        },
+        "readable-stream": {
+          "version": "2.3.6",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "rimraf": {
+          "version": "2.7.1",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "glob": "^7.1.3"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "bundled": true,
+          "optional": true
+        },
+        "safer-buffer": {
+          "version": "2.1.2",
+          "bundled": true,
+          "optional": true
+        },
+        "sax": {
+          "version": "1.2.4",
+          "bundled": true,
+          "optional": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "bundled": true,
+          "optional": true
+        },
+        "set-blocking": {
+          "version": "2.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "signal-exit": {
+          "version": "3.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        },
+        "strip-json-comments": {
+          "version": "2.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "tar": {
+          "version": "4.4.13",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "chownr": "^1.1.1",
+            "fs-minipass": "^1.2.5",
+            "minipass": "^2.8.6",
+            "minizlib": "^1.2.1",
+            "mkdirp": "^0.5.0",
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.3"
+          }
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "wide-align": {
+          "version": "1.1.3",
+          "bundled": true,
+          "optional": true,
+          "requires": {
+            "string-width": "^1.0.2 || 2"
+          }
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "bundled": true,
+          "optional": true
+        }
+      }
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "genfun": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz",
+      "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==",
+      "dev": true
+    },
+    "gensync": {
+      "version": "1.0.0-beta.1",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
+      "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg=="
+    },
+    "get-caller-file": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+      "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w=="
+    },
+    "get-stream": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+      "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+      "requires": {
+        "pump": "^3.0.0"
+      }
+    },
+    "get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "glob": {
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+      "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+      "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+      "requires": {
+        "is-glob": "^3.1.0",
+        "path-dirname": "^1.0.0"
+      },
+      "dependencies": {
+        "is-glob": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+          "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+          "requires": {
+            "is-extglob": "^2.1.0"
+          }
+        }
+      }
+    },
+    "globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+    },
+    "globby": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
+      "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
+      "requires": {
+        "array-union": "^1.0.1",
+        "dir-glob": "^2.0.0",
+        "glob": "^7.1.2",
+        "ignore": "^3.3.5",
+        "pify": "^3.0.0",
+        "slash": "^1.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+        }
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
+    },
+    "hammerjs": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
+      "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
+    },
+    "handle-thing": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
+      "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "has-binary2": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
+      "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
+      "dev": true,
+      "requires": {
+        "isarray": "2.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+          "dev": true
+        }
+      }
+    },
+    "has-cors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+      "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
+      "dev": true
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+    },
+    "has-symbols": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
+    },
+    "has-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+      "requires": {
+        "get-value": "^2.0.6",
+        "has-values": "^1.0.0",
+        "isobject": "^3.0.0"
+      }
+    },
+    "has-values": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+      "requires": {
+        "is-number": "^3.0.0",
+        "kind-of": "^4.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "hash-base": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+      "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "hosted-git-info": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.6.tgz",
+      "integrity": "sha512-Kp6rShEsCHhF5dD3EWKdkgVA8ix90oSUJ0VY4g9goxxa0+f4lx63muTftn0mlJ/+8IESGWyKnP//V2D7S4ZbIQ==",
+      "dev": true
+    },
+    "hpack.js": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+      "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+      "requires": {
+        "inherits": "^2.0.1",
+        "obuf": "^1.0.0",
+        "readable-stream": "^2.0.1",
+        "wbuf": "^1.1.0"
+      }
+    },
+    "html-entities": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz",
+      "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA=="
+    },
+    "html-escaper": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz",
+      "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==",
+      "dev": true
+    },
+    "http-cache-semantics": {
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+      "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==",
+      "dev": true
+    },
+    "http-deceiver": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+      "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc="
+    },
+    "http-errors": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+        }
+      }
+    },
+    "http-parser-js": {
+      "version": "0.4.10",
+      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
+      "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q="
+    },
+    "http-proxy": {
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
+      "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+      "requires": {
+        "eventemitter3": "^4.0.0",
+        "follow-redirects": "^1.0.0",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "http-proxy-agent": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+      "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "4",
+        "debug": "3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "http-proxy-middleware": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
+      "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
+      "requires": {
+        "http-proxy": "^1.17.0",
+        "is-glob": "^4.0.0",
+        "lodash": "^4.17.11",
+        "micromatch": "^3.1.10"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "https-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
+    },
+    "https-proxy-agent": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
+      "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "^4.3.0",
+        "debug": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "humanize-ms": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+      "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+      "dev": true,
+      "requires": {
+        "ms": "^2.0.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+    },
+    "iferr": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+      "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
+    },
+    "ignore": {
+      "version": "3.3.10",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+      "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="
+    },
+    "ignore-walk": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
+      "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
+      "dev": true,
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
+    "image-size": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
+      "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
+      "optional": true
+    },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+    },
+    "import-cwd": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
+      "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=",
+      "requires": {
+        "import-from": "^2.1.0"
+      }
+    },
+    "import-fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+      "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+      "requires": {
+        "caller-path": "^2.0.0",
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "import-from": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
+      "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=",
+      "requires": {
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "import-local": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+      "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+      "requires": {
+        "pkg-dir": "^3.0.0",
+        "resolve-cwd": "^2.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
+    },
+    "indexof": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+      "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+      "dev": true
+    },
+    "infer-owner": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+      "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
+    },
+    "inquirer": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz",
+      "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^3.2.0",
+        "chalk": "^2.4.2",
+        "cli-cursor": "^2.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^2.0.0",
+        "lodash": "^4.17.11",
+        "mute-stream": "0.0.7",
+        "run-async": "^2.2.0",
+        "rxjs": "^6.4.0",
+        "string-width": "^2.1.0",
+        "strip-ansi": "^5.1.0",
+        "through": "^2.3.6"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "internal-ip": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
+      "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==",
+      "requires": {
+        "default-gateway": "^4.2.0",
+        "ipaddr.js": "^1.9.0"
+      }
+    },
+    "interpret": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
+      "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
+      "dev": true
+    },
+    "invariant": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+      "requires": {
+        "loose-envify": "^1.0.0"
+      }
+    },
+    "invert-kv": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+      "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA=="
+    },
+    "ip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+    },
+    "ip-regex": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+      "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
+    },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+    },
+    "is-absolute-url": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz",
+      "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q=="
+    },
+    "is-accessor-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-arguments": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
+      "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA=="
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+    },
+    "is-binary-path": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+      "requires": {
+        "binary-extensions": "^1.0.0"
+      }
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+    },
+    "is-callable": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+      "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
+    },
+    "is-data-descriptor": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
+    },
+    "is-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+      "requires": {
+        "is-accessor-descriptor": "^0.1.6",
+        "is-data-descriptor": "^0.1.4",
+        "kind-of": "^5.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+        }
+      }
+    },
+    "is-directory": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+      "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-path-cwd": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
+      "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ=="
+    },
+    "is-path-in-cwd": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+      "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
+      "requires": {
+        "is-path-inside": "^2.1.0"
+      }
+    },
+    "is-path-inside": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+      "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
+      "requires": {
+        "path-is-inside": "^1.0.2"
+      }
+    },
+    "is-plain-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+      "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+      "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+    },
+    "is-symbol": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
+    },
+    "is-wsl": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+      "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "isbinaryfile": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz",
+      "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
+      "dev": true,
+      "requires": {
+        "buffer-alloc": "^1.2.0"
+      }
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+    },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "istanbul-api": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.6.tgz",
+      "integrity": "sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==",
+      "dev": true,
+      "requires": {
+        "async": "^2.6.2",
+        "compare-versions": "^3.4.0",
+        "fileset": "^2.0.3",
+        "istanbul-lib-coverage": "^2.0.5",
+        "istanbul-lib-hook": "^2.0.7",
+        "istanbul-lib-instrument": "^3.3.0",
+        "istanbul-lib-report": "^2.0.8",
+        "istanbul-lib-source-maps": "^3.0.6",
+        "istanbul-reports": "^2.2.4",
+        "js-yaml": "^3.13.1",
+        "make-dir": "^2.1.0",
+        "minimatch": "^3.0.4",
+        "once": "^1.4.0"
+      },
+      "dependencies": {
+        "istanbul-lib-coverage": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+          "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+          "dev": true
+        },
+        "istanbul-lib-instrument": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
+          "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
+          "dev": true,
+          "requires": {
+            "@babel/generator": "^7.4.0",
+            "@babel/parser": "^7.4.3",
+            "@babel/template": "^7.4.0",
+            "@babel/traverse": "^7.4.3",
+            "@babel/types": "^7.4.0",
+            "istanbul-lib-coverage": "^2.0.5",
+            "semver": "^6.0.0"
+          }
+        }
+      }
+    },
+    "istanbul-lib-coverage": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz",
+      "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg=="
+    },
+    "istanbul-lib-hook": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz",
+      "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==",
+      "dev": true,
+      "requires": {
+        "append-transform": "^1.0.0"
+      }
+    },
+    "istanbul-lib-instrument": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz",
+      "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==",
+      "requires": {
+        "@babel/core": "^7.7.5",
+        "@babel/parser": "^7.7.5",
+        "@babel/template": "^7.7.4",
+        "@babel/traverse": "^7.7.4",
+        "@istanbuljs/schema": "^0.1.2",
+        "istanbul-lib-coverage": "^3.0.0",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+        }
+      }
+    },
+    "istanbul-lib-report": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
+      "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
+      "dev": true,
+      "requires": {
+        "istanbul-lib-coverage": "^2.0.5",
+        "make-dir": "^2.1.0",
+        "supports-color": "^6.1.0"
+      },
+      "dependencies": {
+        "istanbul-lib-coverage": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+          "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+          "dev": true
+        }
+      }
+    },
+    "istanbul-lib-source-maps": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
+      "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "istanbul-lib-coverage": "^2.0.5",
+        "make-dir": "^2.1.0",
+        "rimraf": "^2.6.3",
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "istanbul-lib-coverage": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+          "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "istanbul-reports": {
+      "version": "2.2.7",
+      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz",
+      "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==",
+      "dev": true,
+      "requires": {
+        "html-escaper": "^2.0.0"
+      }
+    },
+    "jasmine": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
+      "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=",
+      "dev": true,
+      "requires": {
+        "exit": "^0.1.2",
+        "glob": "^7.0.6",
+        "jasmine-core": "~2.8.0"
+      },
+      "dependencies": {
+        "jasmine-core": {
+          "version": "2.8.0",
+          "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz",
+          "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=",
+          "dev": true
+        }
+      }
+    },
+    "jasmine-core": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz",
+      "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==",
+      "dev": true
+    },
+    "jasmine-spec-reporter": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
+      "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==",
+      "dev": true,
+      "requires": {
+        "colors": "1.1.2"
+      }
+    },
+    "jasminewd2": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz",
+      "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=",
+      "dev": true
+    },
+    "jest-worker": {
+      "version": "24.9.0",
+      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
+      "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
+      "requires": {
+        "merge-stream": "^2.0.0",
+        "supports-color": "^6.1.0"
+      }
+    },
+    "js-tokens": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+      "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+    },
+    "js-yaml": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "json3": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
+      "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA=="
+    },
+    "json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "jsonparse": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+      "dev": true
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "jszip": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.4.0.tgz",
+      "integrity": "sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==",
+      "requires": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "set-immediate-shim": "~1.0.1"
+      }
+    },
+    "karma": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/karma/-/karma-4.1.0.tgz",
+      "integrity": "sha512-xckiDqyNi512U4dXGOOSyLKPwek6X/vUizSy2f3geYevbLj+UIdvNwbn7IwfUIL2g1GXEPWt/87qFD1fBbl/Uw==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.3.0",
+        "body-parser": "^1.16.1",
+        "braces": "^2.3.2",
+        "chokidar": "^2.0.3",
+        "colors": "^1.1.0",
+        "connect": "^3.6.0",
+        "core-js": "^2.2.0",
+        "di": "^0.0.1",
+        "dom-serialize": "^2.2.0",
+        "flatted": "^2.0.0",
+        "glob": "^7.1.1",
+        "graceful-fs": "^4.1.2",
+        "http-proxy": "^1.13.0",
+        "isbinaryfile": "^3.0.0",
+        "lodash": "^4.17.11",
+        "log4js": "^4.0.0",
+        "mime": "^2.3.1",
+        "minimatch": "^3.0.2",
+        "optimist": "^0.6.1",
+        "qjobs": "^1.1.4",
+        "range-parser": "^1.2.0",
+        "rimraf": "^2.6.0",
+        "safe-buffer": "^5.0.1",
+        "socket.io": "2.1.1",
+        "source-map": "^0.6.1",
+        "tmp": "0.0.33",
+        "useragent": "2.3.0"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.6.11",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
+          "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==",
+          "dev": true
+        },
+        "mime": {
+          "version": "2.4.4",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+          "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "karma-chrome-launcher": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz",
+      "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==",
+      "dev": true,
+      "requires": {
+        "fs-access": "^1.0.0",
+        "which": "^1.2.1"
+      }
+    },
+    "karma-coverage-istanbul-reporter": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.6.tgz",
+      "integrity": "sha512-WFh77RI8bMIKdOvI/1/IBmgnM+Q7NOLhnwG91QJrM8lW+CIXCjTzhhUsT/svLvAkLmR10uWY4RyYbHMLkTglvg==",
+      "dev": true,
+      "requires": {
+        "istanbul-api": "^2.1.6",
+        "minimatch": "^3.0.4"
+      }
+    },
+    "karma-jasmine": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz",
+      "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==",
+      "dev": true,
+      "requires": {
+        "jasmine-core": "^3.3"
+      }
+    },
+    "karma-jasmine-html-reporter": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.2.tgz",
+      "integrity": "sha512-ILBPsXqQ3eomq+oaQsM311/jxsypw5/d0LnZXj26XkfThwq7jZ55A2CFSKJVA5VekbbOGvMyv7d3juZj0SeTxA==",
+      "dev": true
+    },
+    "karma-source-map-support": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz",
+      "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==",
+      "requires": {
+        "source-map-support": "^0.5.5"
+      }
+    },
+    "killable": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
+      "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg=="
+    },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
+    },
+    "lcid": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+      "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+      "requires": {
+        "invert-kv": "^2.0.0"
+      }
+    },
+    "less": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz",
+      "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==",
+      "requires": {
+        "clone": "^2.1.2",
+        "errno": "^0.1.1",
+        "graceful-fs": "^4.1.2",
+        "image-size": "~0.5.0",
+        "mime": "^1.4.1",
+        "mkdirp": "^0.5.0",
+        "promise": "^7.1.1",
+        "request": "^2.83.0",
+        "source-map": "~0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "optional": true
+        }
+      }
+    },
+    "less-loader": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz",
+      "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==",
+      "requires": {
+        "clone": "^2.1.1",
+        "loader-utils": "^1.1.0",
+        "pify": "^4.0.1"
+      }
+    },
+    "leven": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+      "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="
+    },
+    "levenary": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz",
+      "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==",
+      "requires": {
+        "leven": "^3.1.0"
+      }
+    },
+    "license-webpack-plugin": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.2.tgz",
+      "integrity": "sha512-7poZHRla+ae0eEButlwMrPpkXyhNVBf2EHePYWT0jyLnI6311/OXJkTI2sOIRungRpQgU2oDMpro5bSFPT5F0A==",
+      "requires": {
+        "@types/webpack-sources": "^0.1.5",
+        "webpack-sources": "^1.2.0"
+      }
+    },
+    "lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "requires": {
+        "immediate": "~3.0.5"
+      }
+    },
+    "loader-runner": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw=="
+    },
+    "loader-utils": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+      "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+      "requires": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^2.0.0",
+        "json5": "^1.0.1"
+      }
+    },
+    "locate-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+      "requires": {
+        "p-locate": "^3.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+    },
+    "lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
+    },
+    "log4js": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz",
+      "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
+      "dev": true,
+      "requires": {
+        "date-format": "^2.0.0",
+        "debug": "^4.1.1",
+        "flatted": "^2.0.0",
+        "rfdc": "^1.1.4",
+        "streamroller": "^1.0.6"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "loglevel": {
+      "version": "1.6.8",
+      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz",
+      "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA=="
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "requires": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "magic-string": {
+      "version": "0.25.2",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz",
+      "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==",
+      "dev": true,
+      "requires": {
+        "sourcemap-codec": "^1.4.4"
+      }
+    },
+    "make-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+      "requires": {
+        "pify": "^4.0.1",
+        "semver": "^5.6.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
+    "make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
+    },
+    "make-fetch-happen": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz",
+      "integrity": "sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA==",
+      "dev": true,
+      "requires": {
+        "agentkeepalive": "^3.4.1",
+        "cacache": "^11.3.3",
+        "http-cache-semantics": "^3.8.1",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^2.2.1",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "node-fetch-npm": "^2.0.2",
+        "promise-retry": "^1.1.1",
+        "socks-proxy-agent": "^4.0.0",
+        "ssri": "^6.0.0"
+      }
+    },
+    "mamacro": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
+      "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA=="
+    },
+    "map-age-cleaner": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+      "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+      "requires": {
+        "p-defer": "^1.0.0"
+      }
+    },
+    "map-cache": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
+    },
+    "map-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+      "requires": {
+        "object-visit": "^1.0.0"
+      }
+    },
+    "mat-table-exporter": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/mat-table-exporter/-/mat-table-exporter-9.0.0.tgz",
+      "integrity": "sha512-xqvitbmC2dhABjVov0GACG8Nr0NmK83MB6nant4kuMIAVGc3yMZI+Q4F/hC/FHtVELwZuD+H8NFR9V/dqCAWdw==",
+      "requires": {
+        "cdk-table-exporter": "^9.0.0"
+      }
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "mem": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
+      "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
+      "requires": {
+        "map-age-cleaner": "^0.1.1",
+        "mimic-fn": "^2.0.0",
+        "p-is-promise": "^2.0.0"
+      }
+    },
+    "memory-fs": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+      "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+      "requires": {
+        "errno": "^0.1.3",
+        "readable-stream": "^2.0.1"
+      }
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "merge-source-map": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
+      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
+      "requires": {
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "merge-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "micromatch": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "braces": "^2.3.1",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "extglob": "^2.0.4",
+        "fragment-cache": "^0.2.1",
+        "kind-of": "^6.0.2",
+        "nanomatch": "^1.2.9",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.2"
+      }
+    },
+    "miller-rabin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "requires": {
+        "bn.js": "^4.0.0",
+        "brorand": "^1.0.1"
+      }
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
+    },
+    "mime-types": {
+      "version": "2.1.26",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+      "requires": {
+        "mime-db": "1.43.0"
+      }
+    },
+    "mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+    },
+    "mini-css-extract-plugin": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz",
+      "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==",
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "normalize-url": "1.9.1",
+        "schema-utils": "^1.0.0",
+        "webpack-sources": "^1.1.0"
+      }
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+    },
+    "minipass": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+      "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      }
+    },
+    "minizlib": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
+      "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
+      "dev": true,
+      "requires": {
+        "minipass": "^2.9.0"
+      }
+    },
+    "mississippi": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+      "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+      "requires": {
+        "concat-stream": "^1.5.0",
+        "duplexify": "^3.4.2",
+        "end-of-stream": "^1.1.0",
+        "flush-write-stream": "^1.0.0",
+        "from2": "^2.1.0",
+        "parallel-transform": "^1.1.0",
+        "pump": "^3.0.0",
+        "pumpify": "^1.3.3",
+        "stream-each": "^1.1.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "mixin-deep": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+      "requires": {
+        "for-in": "^1.0.2",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "requires": {
+        "minimist": "0.0.8"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+        }
+      }
+    },
+    "move-concurrently": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+      "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+      "requires": {
+        "aproba": "^1.1.1",
+        "copy-concurrently": "^1.0.0",
+        "fs-write-stream-atomic": "^1.0.8",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.3"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "multicast-dns": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
+      "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+      "requires": {
+        "dns-packet": "^1.3.1",
+        "thunky": "^1.0.2"
+      }
+    },
+    "multicast-dns-service-types": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
+      "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
+    },
+    "mute-stream": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+      "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+      "dev": true
+    },
+    "nan": {
+      "version": "2.14.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+      "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+      "optional": true
+    },
+    "nanomatch": {
+      "version": "1.2.13",
+      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "fragment-cache": "^0.2.1",
+        "is-windows": "^1.0.2",
+        "kind-of": "^6.0.2",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      }
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "neo-async": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
+    },
+    "ng4-loading-spinner": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/ng4-loading-spinner/-/ng4-loading-spinner-1.2.3.tgz",
+      "integrity": "sha512-AxokaWmIMSv4YeZcwzSCs0f1PBnn/lgOVpaXuR5PdnNqGkpDema6b2KwONLtdzmL5YqKOpQo8DoyTRhlzGoohQ=="
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
+    },
+    "node-fetch-npm": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
+      "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
+      "dev": true,
+      "requires": {
+        "encoding": "^0.1.11",
+        "json-parse-better-errors": "^1.0.0",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "node-forge": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
+      "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ=="
+    },
+    "node-libs-browser": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+      "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+      "requires": {
+        "assert": "^1.1.1",
+        "browserify-zlib": "^0.2.0",
+        "buffer": "^4.3.0",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "^1.0.0",
+        "crypto-browserify": "^3.11.0",
+        "domain-browser": "^1.1.1",
+        "events": "^3.0.0",
+        "https-browserify": "^1.0.0",
+        "os-browserify": "^0.3.0",
+        "path-browserify": "0.0.1",
+        "process": "^0.11.10",
+        "punycode": "^1.2.4",
+        "querystring-es3": "^0.2.0",
+        "readable-stream": "^2.3.3",
+        "stream-browserify": "^2.0.1",
+        "stream-http": "^2.7.2",
+        "string_decoder": "^1.0.0",
+        "timers-browserify": "^2.0.4",
+        "tty-browserify": "0.0.0",
+        "url": "^0.11.0",
+        "util": "^0.11.0",
+        "vm-browserify": "^1.0.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        }
+      }
+    },
+    "node-releases": {
+      "version": "1.1.53",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz",
+      "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ=="
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+    },
+    "normalize-range": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+      "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
+    },
+    "normalize-url": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
+      "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
+      "requires": {
+        "object-assign": "^4.0.1",
+        "prepend-http": "^1.0.0",
+        "query-string": "^4.1.0",
+        "sort-keys": "^1.0.0"
+      }
+    },
+    "npm-bundled": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
+      "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
+      "dev": true,
+      "requires": {
+        "npm-normalize-package-bin": "^1.0.1"
+      }
+    },
+    "npm-normalize-package-bin": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
+      "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
+      "dev": true
+    },
+    "npm-package-arg": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz",
+      "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.6.0",
+        "osenv": "^0.1.5",
+        "semver": "^5.5.0",
+        "validate-npm-package-name": "^3.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "npm-packlist": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
+      "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
+      "dev": true,
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1",
+        "npm-normalize-package-bin": "^1.0.1"
+      }
+    },
+    "npm-pick-manifest": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz",
+      "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1",
+        "npm-package-arg": "^6.0.0",
+        "semver": "^5.4.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "npm-registry-fetch": {
+      "version": "3.9.1",
+      "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.9.1.tgz",
+      "integrity": "sha512-VQCEZlydXw4AwLROAXWUR7QDfe2Y8Id/vpAgp6TI1/H78a4SiQ1kQrKZALm5/zxM5n4HIi+aYb+idUAV/RuY0Q==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.3.4",
+        "bluebird": "^3.5.1",
+        "figgy-pudding": "^3.4.1",
+        "lru-cache": "^5.1.1",
+        "make-fetch-happen": "^4.0.2",
+        "npm-package-arg": "^6.1.0"
+      }
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
+    "null-check": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz",
+      "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=",
+      "dev": true
+    },
+    "num2fraction": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+      "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4="
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object-component": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+      "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
+      "dev": true
+    },
+    "object-copy": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+      "requires": {
+        "copy-descriptor": "^0.1.0",
+        "define-property": "^0.2.5",
+        "kind-of": "^3.0.3"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "object-inspect": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
+      "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
+    },
+    "object-is": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz",
+      "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+    },
+    "object-visit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+      "requires": {
+        "isobject": "^3.0.0"
+      }
+    },
+    "object.assign": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+      "requires": {
+        "define-properties": "^1.1.2",
+        "function-bind": "^1.1.1",
+        "has-symbols": "^1.0.0",
+        "object-keys": "^1.0.11"
+      }
+    },
+    "object.pick": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "obuf": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+      "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "onetime": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+      "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^1.0.0"
+      },
+      "dependencies": {
+        "mimic-fn": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+          "dev": true
+        }
+      }
+    },
+    "open": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/open/-/open-6.2.0.tgz",
+      "integrity": "sha512-Vxf6HJkwrqmvh9UAID3MnMYXntbTxKLOSfOnO7LJdzPf3NE3KQYFNV0/Lcz2VAndbRFil58XVCyh8tiX11fiYw==",
+      "dev": true,
+      "requires": {
+        "is-wsl": "^1.1.0"
+      }
+    },
+    "opn": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
+      "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
+      "requires": {
+        "is-wsl": "^1.1.0"
+      }
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+      "dev": true,
+      "requires": {
+        "minimist": "~0.0.1",
+        "wordwrap": "~0.0.2"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "0.0.10",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+          "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
+          "dev": true
+        }
+      }
+    },
+    "original": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
+      "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
+      "requires": {
+        "url-parse": "^1.4.3"
+      }
+    },
+    "os-browserify": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc="
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+      "dev": true
+    },
+    "os-locale": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+      "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+      "requires": {
+        "execa": "^1.0.0",
+        "lcid": "^2.0.0",
+        "mem": "^4.0.0"
+      }
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true
+    },
+    "osenv": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "dev": true,
+      "requires": {
+        "os-homedir": "^1.0.0",
+        "os-tmpdir": "^1.0.0"
+      }
+    },
+    "p-defer": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+      "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww="
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+    },
+    "p-is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+      "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg=="
+    },
+    "p-limit": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
+      "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+      "requires": {
+        "p-try": "^2.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+      "requires": {
+        "p-limit": "^2.0.0"
+      }
+    },
+    "p-map": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+      "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="
+    },
+    "p-retry": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
+      "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
+      "requires": {
+        "retry": "^0.12.0"
+      },
+      "dependencies": {
+        "retry": {
+          "version": "0.12.0",
+          "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+          "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
+        }
+      }
+    },
+    "p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+    },
+    "pacote": {
+      "version": "9.5.0",
+      "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.0.tgz",
+      "integrity": "sha512-aUplXozRbzhaJO48FaaeClmN+2Mwt741MC6M3bevIGZwdCaP7frXzbUOfOWa91FPHoLITzG0hYaKY363lxO3bg==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.3",
+        "cacache": "^11.3.2",
+        "figgy-pudding": "^3.5.1",
+        "get-stream": "^4.1.0",
+        "glob": "^7.1.3",
+        "lru-cache": "^5.1.1",
+        "make-fetch-happen": "^4.0.1",
+        "minimatch": "^3.0.4",
+        "minipass": "^2.3.5",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "normalize-package-data": "^2.4.0",
+        "npm-package-arg": "^6.1.0",
+        "npm-packlist": "^1.1.12",
+        "npm-pick-manifest": "^2.2.3",
+        "npm-registry-fetch": "^3.8.0",
+        "osenv": "^0.1.5",
+        "promise-inflight": "^1.0.1",
+        "promise-retry": "^1.1.1",
+        "protoduck": "^5.0.1",
+        "rimraf": "^2.6.2",
+        "safe-buffer": "^5.1.2",
+        "semver": "^5.6.0",
+        "ssri": "^6.0.1",
+        "tar": "^4.4.8",
+        "unique-filename": "^1.1.1",
+        "which": "^1.3.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
+    "parallel-transform": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+      "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+      "requires": {
+        "cyclist": "^1.0.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.1.5"
+      }
+    },
+    "parse-asn1": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+      "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+      "requires": {
+        "asn1.js": "^4.0.0",
+        "browserify-aes": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.0",
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "parse-json": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+      "requires": {
+        "error-ex": "^1.3.1",
+        "json-parse-better-errors": "^1.0.1"
+      }
+    },
+    "parse5": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+      "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
+    },
+    "parseqs": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+      "dev": true,
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseuri": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+      "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+      "dev": true,
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "pascalcase": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
+    },
+    "path-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ=="
+    },
+    "path-dirname": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM="
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "path-type": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+      "requires": {
+        "pify": "^3.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+        }
+      }
+    },
+    "pbkdf2": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
+    "pify": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "requires": {
+        "pinkie": "^2.0.0"
+      }
+    },
+    "pkg-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+      "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+      "requires": {
+        "find-up": "^3.0.0"
+      }
+    },
+    "pkg-up": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
+      "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
+      "requires": {
+        "find-up": "^2.1.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+          "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+          "requires": {
+            "locate-path": "^2.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+          "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+          "requires": {
+            "p-locate": "^2.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+          "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+          "requires": {
+            "p-try": "^1.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+          "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+          "requires": {
+            "p-limit": "^1.1.0"
+          }
+        },
+        "p-try": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+          "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
+        }
+      }
+    },
+    "portfinder": {
+      "version": "1.0.25",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
+      "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
+      "requires": {
+        "async": "^2.6.2",
+        "debug": "^3.1.1",
+        "mkdirp": "^0.5.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "posix-character-classes": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
+    },
+    "postcss": {
+      "version": "7.0.17",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz",
+      "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==",
+      "requires": {
+        "chalk": "^2.4.2",
+        "source-map": "^0.6.1",
+        "supports-color": "^6.1.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "postcss-import": {
+      "version": "12.0.1",
+      "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz",
+      "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==",
+      "requires": {
+        "postcss": "^7.0.1",
+        "postcss-value-parser": "^3.2.3",
+        "read-cache": "^1.0.0",
+        "resolve": "^1.1.7"
+      },
+      "dependencies": {
+        "postcss-value-parser": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+          "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
+        }
+      }
+    },
+    "postcss-load-config": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz",
+      "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==",
+      "requires": {
+        "cosmiconfig": "^5.0.0",
+        "import-cwd": "^2.0.0"
+      }
+    },
+    "postcss-loader": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz",
+      "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==",
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "postcss": "^7.0.0",
+        "postcss-load-config": "^2.0.0",
+        "schema-utils": "^1.0.0"
+      }
+    },
+    "postcss-value-parser": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
+      "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg=="
+    },
+    "prepend-http": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
+    },
+    "primeicons": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-2.0.0.tgz",
+      "integrity": "sha512-GJTCeMSQU8UU1GqbsaDrg/IH+b/vSinJQl52NVpdJ7sShYLZA8Eq6jLF48Ye3N/dQloGrE07i7XsZvxQ9pNbqg=="
+    },
+    "primeng": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/primeng/-/primeng-8.1.1.tgz",
+      "integrity": "sha512-8dP8A3qkmOvDTe97kCeFvoCk6oPNDdgMTdT4UBzhC4ua5wM7i7pjYXuKzAXm3oEFf+iSEB6WqzzfSqyZdbEA+w=="
+    },
+    "printj": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
+      "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ=="
+    },
+    "private": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+      "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg=="
+    },
+    "process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "promise": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+      "optional": true,
+      "requires": {
+        "asap": "~2.0.3"
+      }
+    },
+    "promise-inflight": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+      "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
+    },
+    "promise-retry": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
+      "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
+      "dev": true,
+      "requires": {
+        "err-code": "^1.0.0",
+        "retry": "^0.10.0"
+      }
+    },
+    "protoduck": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz",
+      "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
+      "dev": true,
+      "requires": {
+        "genfun": "^5.0.0"
+      }
+    },
+    "protractor": {
+      "version": "5.4.3",
+      "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.3.tgz",
+      "integrity": "sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA==",
+      "dev": true,
+      "requires": {
+        "@types/q": "^0.0.32",
+        "@types/selenium-webdriver": "^3.0.0",
+        "blocking-proxy": "^1.0.0",
+        "browserstack": "^1.5.1",
+        "chalk": "^1.1.3",
+        "glob": "^7.0.3",
+        "jasmine": "2.8.0",
+        "jasminewd2": "^2.1.0",
+        "optimist": "~0.6.0",
+        "q": "1.4.1",
+        "saucelabs": "^1.5.0",
+        "selenium-webdriver": "3.6.0",
+        "source-map-support": "~0.4.0",
+        "webdriver-js-extender": "2.1.0",
+        "webdriver-manager": "^12.0.6"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "del": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+          "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+          "dev": true,
+          "requires": {
+            "globby": "^5.0.0",
+            "is-path-cwd": "^1.0.0",
+            "is-path-in-cwd": "^1.0.0",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0",
+            "rimraf": "^2.2.8"
+          }
+        },
+        "globby": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+          "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+          "dev": true,
+          "requires": {
+            "array-union": "^1.0.1",
+            "arrify": "^1.0.0",
+            "glob": "^7.0.3",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "is-path-cwd": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+          "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+          "dev": true
+        },
+        "is-path-in-cwd": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
+          "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
+          "dev": true,
+          "requires": {
+            "is-path-inside": "^1.0.0"
+          }
+        },
+        "is-path-inside": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+          "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+          "dev": true,
+          "requires": {
+            "path-is-inside": "^1.0.1"
+          }
+        },
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        },
+        "source-map-support": {
+          "version": "0.4.18",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
+          "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+          "dev": true,
+          "requires": {
+            "source-map": "^0.5.6"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        },
+        "webdriver-manager": {
+          "version": "12.1.7",
+          "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.7.tgz",
+          "integrity": "sha512-XINj6b8CYuUYC93SG3xPkxlyUc3IJbD6Vvo75CVGuG9uzsefDzWQrhz0Lq8vbPxtb4d63CZdYophF8k8Or/YiA==",
+          "dev": true,
+          "requires": {
+            "adm-zip": "^0.4.9",
+            "chalk": "^1.1.1",
+            "del": "^2.2.0",
+            "glob": "^7.0.3",
+            "ini": "^1.3.4",
+            "minimist": "^1.2.0",
+            "q": "^1.4.1",
+            "request": "^2.87.0",
+            "rimraf": "^2.5.2",
+            "semver": "^5.3.0",
+            "xml2js": "^0.4.17"
+          }
+        }
+      }
+    },
+    "proxy-addr": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+      "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "psl": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
+      "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
+    },
+    "public-encrypt": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "parse-asn1": "^5.0.0",
+        "randombytes": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "pumpify": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+      "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+      "requires": {
+        "duplexify": "^3.6.0",
+        "inherits": "^2.0.3",
+        "pump": "^2.0.0"
+      },
+      "dependencies": {
+        "pump": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+          "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+          "requires": {
+            "end-of-stream": "^1.1.0",
+            "once": "^1.3.1"
+          }
+        }
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "q": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz",
+      "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=",
+      "dev": true
+    },
+    "qjobs": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
+      "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+    },
+    "query-string": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+      "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+      "requires": {
+        "object-assign": "^4.1.0",
+        "strict-uri-encode": "^1.0.0"
+      }
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+    },
+    "querystring-es3": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
+    },
+    "querystringify": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+      "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "randomfill": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "requires": {
+        "randombytes": "^2.0.5",
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "dependencies": {
+        "bytes": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+          "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+        }
+      }
+    },
+    "raw-loader": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz",
+      "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==",
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "schema-utils": "^2.0.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+          "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+          "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+        },
+        "schema-utils": {
+          "version": "2.6.6",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
+          "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
+          "requires": {
+            "ajv": "^6.12.0",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "read-cache": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+      "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=",
+      "requires": {
+        "pify": "^2.3.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+        }
+      }
+    },
+    "read-package-json": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz",
+      "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.1",
+        "graceful-fs": "^4.1.2",
+        "json-parse-better-errors": "^1.0.1",
+        "normalize-package-data": "^2.0.0",
+        "npm-normalize-package-bin": "^1.0.0"
+      }
+    },
+    "read-package-tree": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.2.tgz",
+      "integrity": "sha512-rW3XWUUkhdKmN2JKB4FL563YAgtINifso5KShykufR03nJ5loGFlkUMe1g/yxmqX073SoYYTsgXu7XdDinKZuA==",
+      "dev": true,
+      "requires": {
+        "debuglog": "^1.0.1",
+        "dezalgo": "^1.0.0",
+        "once": "^1.3.0",
+        "read-package-json": "^2.0.0",
+        "readdir-scoped-modules": "^1.0.0"
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "readdir-scoped-modules": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
+      "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
+      "dev": true,
+      "requires": {
+        "debuglog": "^1.0.1",
+        "dezalgo": "^1.0.0",
+        "graceful-fs": "^4.1.2",
+        "once": "^1.3.0"
+      }
+    },
+    "readdirp": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+      "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "micromatch": "^3.1.10",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+      "dev": true,
+      "requires": {
+        "resolve": "^1.1.6"
+      }
+    },
+    "reflect-metadata": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
+      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
+      "dev": true
+    },
+    "regenerate": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+      "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg=="
+    },
+    "regenerate-unicode-properties": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
+      "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
+      "requires": {
+        "regenerate": "^1.4.0"
+      }
+    },
+    "regenerator-runtime": {
+      "version": "0.13.3",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+      "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+    },
+    "regenerator-transform": {
+      "version": "0.14.4",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz",
+      "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==",
+      "requires": {
+        "@babel/runtime": "^7.8.4",
+        "private": "^0.1.8"
+      }
+    },
+    "regex-not": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+      "requires": {
+        "extend-shallow": "^3.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "regexp.prototype.flags": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
+      "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "regexpu-core": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+      "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+      "dev": true,
+      "requires": {
+        "regenerate": "^1.2.1",
+        "regjsgen": "^0.2.0",
+        "regjsparser": "^0.1.4"
+      }
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+      "dev": true
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+      "dev": true,
+      "requires": {
+        "jsesc": "~0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        }
+      }
+    },
+    "remove-trailing-separator": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
+    },
+    "repeat-element": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+      "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+    },
+    "request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+    },
+    "require-main-filename": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+      "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
+    },
+    "resolve": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
+      "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "resolve-cwd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+      "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+      "requires": {
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "resolve-from": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+      "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
+    },
+    "restore-cursor": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+      "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+      "dev": true,
+      "requires": {
+        "onetime": "^2.0.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "ret": {
+      "version": "0.1.15",
+      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
+    },
+    "retry": {
+      "version": "0.10.1",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+      "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
+      "dev": true
+    },
+    "rfdc": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+      "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "run-async": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz",
+      "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==",
+      "dev": true,
+      "requires": {
+        "is-promise": "^2.1.0"
+      }
+    },
+    "run-queue": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+      "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+      "requires": {
+        "aproba": "^1.1.1"
+      }
+    },
+    "rxjs": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+      "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safe-regex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+      "requires": {
+        "ret": "~0.1.10"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "sass": {
+      "version": "1.22.9",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.9.tgz",
+      "integrity": "sha512-FzU1X2V8DlnqabrL4u7OBwD2vcOzNMongEJEx3xMEhWY/v26FFR3aG0hyeu2T965sfR0E9ufJwmG+Qjz78vFPQ==",
+      "requires": {
+        "chokidar": ">=2.0.0 <4.0.0"
+      }
+    },
+    "sass-loader": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.2.0.tgz",
+      "integrity": "sha512-h8yUWaWtsbuIiOCgR9fd9c2lRXZ2uG+h8Dzg/AGNj+Hg/3TO8+BBAW9mEP+mh8ei+qBKqSJ0F1FLlYjNBc61OA==",
+      "requires": {
+        "clone-deep": "^4.0.1",
+        "loader-utils": "^1.0.1",
+        "neo-async": "^2.5.0",
+        "pify": "^4.0.1",
+        "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
+    "saucelabs": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz",
+      "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==",
+      "dev": true,
+      "requires": {
+        "https-proxy-agent": "^2.2.1"
+      }
+    },
+    "sax": {
+      "version": "0.5.8",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
+      "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
+    },
+    "schema-utils": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+      "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+      "requires": {
+        "ajv": "^6.1.0",
+        "ajv-errors": "^1.0.0",
+        "ajv-keywords": "^3.1.0"
+      }
+    },
+    "select-hose": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+      "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
+    },
+    "selenium-webdriver": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz",
+      "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==",
+      "dev": true,
+      "requires": {
+        "jszip": "^3.1.3",
+        "rimraf": "^2.5.4",
+        "tmp": "0.0.30",
+        "xml2js": "^0.4.17"
+      },
+      "dependencies": {
+        "tmp": {
+          "version": "0.0.30",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz",
+          "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=",
+          "dev": true,
+          "requires": {
+            "os-tmpdir": "~1.0.1"
+          }
+        }
+      }
+    },
+    "selfsigned": {
+      "version": "1.10.7",
+      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
+      "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
+      "requires": {
+        "node-forge": "0.9.0"
+      }
+    },
+    "semver": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz",
+      "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ=="
+    },
+    "semver-dsl": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
+      "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=",
+      "dev": true,
+      "requires": {
+        "semver": "^5.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "semver-intersect": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz",
+      "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==",
+      "dev": true,
+      "requires": {
+        "semver": "^5.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+        }
+      }
+    },
+    "serialize-javascript": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
+      "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
+    },
+    "serve-index": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+      "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+      "requires": {
+        "accepts": "~1.3.4",
+        "batch": "0.6.1",
+        "debug": "2.6.9",
+        "escape-html": "~1.0.3",
+        "http-errors": "~1.6.2",
+        "mime-types": "~2.1.17",
+        "parseurl": "~1.3.2"
+      },
+      "dependencies": {
+        "http-errors": {
+          "version": "1.6.3",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+          "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+          "requires": {
+            "depd": "~1.1.2",
+            "inherits": "2.0.3",
+            "setprototypeof": "1.1.0",
+            "statuses": ">= 1.4.0 < 2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+        },
+        "setprototypeof": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+          "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+    },
+    "set-immediate-shim": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+      "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
+    },
+    "set-value": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "shallow-clone": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+      "requires": {
+        "kind-of": "^6.0.2"
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+    },
+    "shelljs": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
+      "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.0.0",
+        "interpret": "^1.0.0",
+        "rechoir": "^0.6.2"
+      }
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+    },
+    "slash": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+      "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
+    },
+    "smart-buffer": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
+      "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
+      "dev": true
+    },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "socket.io": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz",
+      "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
+      "dev": true,
+      "requires": {
+        "debug": "~3.1.0",
+        "engine.io": "~3.2.0",
+        "has-binary2": "~1.0.2",
+        "socket.io-adapter": "~1.1.0",
+        "socket.io-client": "2.1.1",
+        "socket.io-parser": "~3.2.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "socket.io-adapter": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
+      "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==",
+      "dev": true
+    },
+    "socket.io-client": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz",
+      "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==",
+      "dev": true,
+      "requires": {
+        "backo2": "1.0.2",
+        "base64-arraybuffer": "0.1.5",
+        "component-bind": "1.0.0",
+        "component-emitter": "1.2.1",
+        "debug": "~3.1.0",
+        "engine.io-client": "~3.2.0",
+        "has-binary2": "~1.0.2",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "object-component": "0.0.3",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "socket.io-parser": "~3.2.0",
+        "to-array": "0.1.4"
+      },
+      "dependencies": {
+        "component-emitter": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+          "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+          "dev": true
+        },
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "socket.io-parser": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
+      "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
+      "dev": true,
+      "requires": {
+        "component-emitter": "1.2.1",
+        "debug": "~3.1.0",
+        "isarray": "2.0.1"
+      },
+      "dependencies": {
+        "component-emitter": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+          "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+          "dev": true
+        },
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+          "dev": true
+        }
+      }
+    },
+    "sockjs": {
+      "version": "0.3.19",
+      "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
+      "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
+      "requires": {
+        "faye-websocket": "^0.10.0",
+        "uuid": "^3.0.1"
+      }
+    },
+    "sockjs-client": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz",
+      "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==",
+      "requires": {
+        "debug": "^3.2.5",
+        "eventsource": "^1.0.7",
+        "faye-websocket": "~0.11.1",
+        "inherits": "^2.0.3",
+        "json3": "^3.3.2",
+        "url-parse": "^1.4.3"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "faye-websocket": {
+          "version": "0.11.3",
+          "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
+          "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
+          "requires": {
+            "websocket-driver": ">=0.5.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "socks": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
+      "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
+      "dev": true,
+      "requires": {
+        "ip": "1.1.5",
+        "smart-buffer": "^4.1.0"
+      }
+    },
+    "socks-proxy-agent": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
+      "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "~4.2.1",
+        "socks": "~2.3.2"
+      },
+      "dependencies": {
+        "agent-base": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+          "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+          "dev": true,
+          "requires": {
+            "es6-promisify": "^5.0.0"
+          }
+        }
+      }
+    },
+    "sort-keys": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+      "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+      "requires": {
+        "is-plain-obj": "^1.0.0"
+      }
+    },
+    "source-list-map": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
+    },
+    "source-map": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
+    },
+    "source-map-loader": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz",
+      "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==",
+      "requires": {
+        "async": "^2.5.0",
+        "loader-utils": "^1.1.0"
+      }
+    },
+    "source-map-resolve": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+      "requires": {
+        "atob": "^2.1.2",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "source-map-support": {
+      "version": "0.5.12",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
+      "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "source-map-url": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+      "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
+    },
+    "sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+    },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
+    },
+    "spdy": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
+      "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
+      "requires": {
+        "debug": "^4.1.0",
+        "handle-thing": "^2.0.0",
+        "http-deceiver": "^1.2.7",
+        "select-hose": "^2.0.0",
+        "spdy-transport": "^3.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "spdy-transport": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+      "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
+      "requires": {
+        "debug": "^4.1.0",
+        "detect-node": "^2.0.4",
+        "hpack.js": "^2.1.6",
+        "obuf": "^1.1.2",
+        "readable-stream": "^3.0.6",
+        "wbuf": "^1.7.3"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
+      }
+    },
+    "speed-measure-webpack-plugin": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz",
+      "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==",
+      "requires": {
+        "chalk": "^2.0.1"
+      }
+    },
+    "split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "requires": {
+        "extend-shallow": "^3.0.0"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
+    "ssf": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.10.3.tgz",
+      "integrity": "sha512-pRuUdW0WwyB2doSqqjWyzwCD6PkfxpHAHdZp39K3dp/Hq7f+xfMwNAWIi16DyrRg4gg9c/RvLYkJTSawTPTm1w==",
+      "requires": {
+        "frac": "~1.1.2"
+      }
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "ssri": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+      "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+      "requires": {
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "static-extend": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+      "requires": {
+        "define-property": "^0.2.5",
+        "object-copy": "^0.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "stream-browserify": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+      "requires": {
+        "inherits": "~2.0.1",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "stream-each": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+      "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "stream-http": {
+      "version": "2.8.3",
+      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+      "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+      "requires": {
+        "builtin-status-codes": "^3.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.3.6",
+        "to-arraybuffer": "^1.0.0",
+        "xtend": "^4.0.0"
+      }
+    },
+    "stream-shift": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
+    },
+    "streamroller": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz",
+      "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
+      "dev": true,
+      "requires": {
+        "async": "^2.6.2",
+        "date-format": "^2.0.0",
+        "debug": "^3.2.6",
+        "fs-extra": "^7.0.1",
+        "lodash": "^4.17.14"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "strict-uri-encode": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+      "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
+    },
+    "string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "requires": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "string.prototype.trimend": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
+      "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "string.prototype.trimleft": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
+      "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5",
+        "string.prototype.trimstart": "^1.0.0"
+      }
+    },
+    "string.prototype.trimright": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
+      "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5",
+        "string.prototype.trimend": "^1.0.0"
+      }
+    },
+    "string.prototype.trimstart": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
+      "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+    },
+    "style-loader": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz",
+      "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==",
+      "requires": {
+        "loader-utils": "^1.2.3",
+        "schema-utils": "^2.0.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+          "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+          "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+        },
+        "schema-utils": {
+          "version": "2.6.6",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
+          "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
+          "requires": {
+            "ajv": "^6.12.0",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "stylus": {
+      "version": "0.54.5",
+      "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz",
+      "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=",
+      "requires": {
+        "css-parse": "1.7.x",
+        "debug": "*",
+        "glob": "7.0.x",
+        "mkdirp": "0.5.x",
+        "sax": "0.5.x",
+        "source-map": "0.1.x"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.0.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
+          "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.2",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "source-map": {
+          "version": "0.1.43",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+          "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
+    "stylus-loader": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz",
+      "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==",
+      "requires": {
+        "loader-utils": "^1.0.2",
+        "lodash.clonedeep": "^4.5.0",
+        "when": "~3.6.x"
+      }
+    },
+    "supports-color": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+      "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "symbol-observable": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+      "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+      "dev": true
+    },
+    "tapable": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+      "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
+    },
+    "tar": {
+      "version": "4.4.13",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
+      "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
+      "dev": true,
+      "requires": {
+        "chownr": "^1.1.1",
+        "fs-minipass": "^1.2.5",
+        "minipass": "^2.8.6",
+        "minizlib": "^1.2.1",
+        "mkdirp": "^0.5.0",
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.3"
+      }
+    },
+    "terser": {
+      "version": "4.6.3",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz",
+      "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==",
+      "requires": {
+        "commander": "^2.20.0",
+        "source-map": "~0.6.1",
+        "source-map-support": "~0.5.12"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "terser-webpack-plugin": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
+      "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
+      "requires": {
+        "cacache": "^12.0.2",
+        "find-cache-dir": "^2.1.0",
+        "is-wsl": "^1.1.0",
+        "schema-utils": "^1.0.0",
+        "serialize-javascript": "^2.1.2",
+        "source-map": "^0.6.1",
+        "terser": "^4.1.2",
+        "webpack-sources": "^1.4.0",
+        "worker-farm": "^1.7.0"
+      },
+      "dependencies": {
+        "cacache": {
+          "version": "12.0.4",
+          "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+          "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+          "requires": {
+            "bluebird": "^3.5.5",
+            "chownr": "^1.1.1",
+            "figgy-pudding": "^3.5.1",
+            "glob": "^7.1.4",
+            "graceful-fs": "^4.1.15",
+            "infer-owner": "^1.0.3",
+            "lru-cache": "^5.1.1",
+            "mississippi": "^3.0.0",
+            "mkdirp": "^0.5.1",
+            "move-concurrently": "^1.0.1",
+            "promise-inflight": "^1.0.1",
+            "rimraf": "^2.6.3",
+            "ssri": "^6.0.1",
+            "unique-filename": "^1.1.1",
+            "y18n": "^4.0.0"
+          }
+        },
+        "find-cache-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+          "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+          "requires": {
+            "commondir": "^1.0.1",
+            "make-dir": "^2.0.0",
+            "pkg-dir": "^3.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      }
+    },
+    "thunky": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
+      "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
+    },
+    "timers-browserify": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+      "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+      "requires": {
+        "setimmediate": "^1.0.4"
+      }
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "to-array": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+      "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
+      "dev": true
+    },
+    "to-arraybuffer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+      "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M="
+    },
+    "to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
+    },
+    "to-object-path": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "to-regex": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+      "requires": {
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "regex-not": "^1.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+      "requires": {
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1"
+      }
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+    },
+    "tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "requires": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      }
+    },
+    "tree-kill": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+      "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
+    },
+    "ts-node": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
+      "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+      "dev": true,
+      "requires": {
+        "arrify": "^1.0.0",
+        "buffer-from": "^1.1.0",
+        "diff": "^3.1.0",
+        "make-error": "^1.1.1",
+        "minimist": "^1.2.0",
+        "mkdirp": "^0.5.1",
+        "source-map-support": "^0.5.6",
+        "yn": "^2.0.0"
+      }
+    },
+    "tslib": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.0.tgz",
+      "integrity": "sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg=="
+    },
+    "tslint": {
+      "version": "5.15.0",
+      "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.15.0.tgz",
+      "integrity": "sha512-6bIEujKR21/3nyeoX2uBnE8s+tMXCQXhqMmaIPJpHmXJoBJPTLcI7/VHRtUwMhnLVdwLqqY3zmd8Dxqa5CVdJA==",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "^6.22.0",
+        "builtin-modules": "^1.1.1",
+        "chalk": "^2.3.0",
+        "commander": "^2.12.1",
+        "diff": "^3.2.0",
+        "glob": "^7.1.1",
+        "js-yaml": "^3.13.0",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "resolve": "^1.3.2",
+        "semver": "^5.3.0",
+        "tslib": "^1.8.0",
+        "tsutils": "^2.29.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "tsutils": {
+      "version": "2.29.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+      "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.8.1"
+      }
+    },
+    "tty-browserify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+      "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+    },
+    "typescript": {
+      "version": "3.4.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
+      "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
+      "dev": true
+    },
+    "ultron": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
+      "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
+      "dev": true
+    },
+    "unicode-canonical-property-names-ecmascript": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+      "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ=="
+    },
+    "unicode-match-property-ecmascript": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+      "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+      "requires": {
+        "unicode-canonical-property-names-ecmascript": "^1.0.4",
+        "unicode-property-aliases-ecmascript": "^1.0.4"
+      }
+    },
+    "unicode-match-property-value-ecmascript": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
+      "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ=="
+    },
+    "unicode-property-aliases-ecmascript": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
+      "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg=="
+    },
+    "union-value": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+      "requires": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^2.0.1"
+      }
+    },
+    "unique-filename": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+      "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+      "requires": {
+        "unique-slug": "^2.0.0"
+      }
+    },
+    "unique-slug": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+      "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+      "requires": {
+        "imurmurhash": "^0.1.4"
+      }
+    },
+    "universal-analytics": {
+      "version": "0.4.20",
+      "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz",
+      "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.0.0",
+        "request": "^2.88.0",
+        "uuid": "^3.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+      "dev": true
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "unset-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+      "requires": {
+        "has-value": "^0.3.1",
+        "isobject": "^3.0.0"
+      },
+      "dependencies": {
+        "has-value": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+          "requires": {
+            "get-value": "^2.0.3",
+            "has-values": "^0.1.4",
+            "isobject": "^2.0.0"
+          },
+          "dependencies": {
+            "isobject": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+              "requires": {
+                "isarray": "1.0.0"
+              }
+            }
+          }
+        },
+        "has-values": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
+        }
+      }
+    },
+    "upath": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+      "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "requires": {
+        "punycode": "1.3.2",
+        "querystring": "0.2.0"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+        }
+      }
+    },
+    "url-parse": {
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+      "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+      "requires": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "use": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
+    },
+    "useragent": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+      "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
+      "dev": true,
+      "requires": {
+        "lru-cache": "4.1.x",
+        "tmp": "0.0.x"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "4.1.5",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+          "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+          "dev": true,
+          "requires": {
+            "pseudomap": "^1.0.2",
+            "yallist": "^2.1.2"
+          }
+        },
+        "yallist": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "dev": true
+        }
+      }
+    },
+    "util": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+      "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+      "requires": {
+        "inherits": "2.0.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+        }
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "validate-npm-package-name": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+      "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+      "dev": true,
+      "requires": {
+        "builtins": "^1.0.3"
+      }
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "vm-browserify": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
+    },
+    "void-elements": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+      "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
+      "dev": true
+    },
+    "watchpack": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz",
+      "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==",
+      "requires": {
+        "chokidar": "^2.1.8",
+        "graceful-fs": "^4.1.2",
+        "neo-async": "^2.5.0"
+      }
+    },
+    "wbuf": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+      "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+      "requires": {
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "webdriver-js-extender": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz",
+      "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==",
+      "dev": true,
+      "requires": {
+        "@types/selenium-webdriver": "^3.0.0",
+        "selenium-webdriver": "^3.0.1"
+      }
+    },
+    "webpack": {
+      "version": "4.39.2",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.2.tgz",
+      "integrity": "sha512-AKgTfz3xPSsEibH00JfZ9sHXGUwIQ6eZ9tLN8+VLzachk1Cw2LVmy+4R7ZiwTa9cZZ15tzySjeMui/UnSCAZhA==",
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-module-context": "1.8.5",
+        "@webassemblyjs/wasm-edit": "1.8.5",
+        "@webassemblyjs/wasm-parser": "1.8.5",
+        "acorn": "^6.2.1",
+        "ajv": "^6.10.2",
+        "ajv-keywords": "^3.4.1",
+        "chrome-trace-event": "^1.0.2",
+        "enhanced-resolve": "^4.1.0",
+        "eslint-scope": "^4.0.3",
+        "json-parse-better-errors": "^1.0.2",
+        "loader-runner": "^2.4.0",
+        "loader-utils": "^1.2.3",
+        "memory-fs": "^0.4.1",
+        "micromatch": "^3.1.10",
+        "mkdirp": "^0.5.1",
+        "neo-async": "^2.6.1",
+        "node-libs-browser": "^2.2.1",
+        "schema-utils": "^1.0.0",
+        "tapable": "^1.1.3",
+        "terser-webpack-plugin": "^1.4.1",
+        "watchpack": "^1.6.0",
+        "webpack-sources": "^1.4.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+          "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+          "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+        }
+      }
+    },
+    "webpack-core": {
+      "version": "0.6.9",
+      "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz",
+      "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=",
+      "requires": {
+        "source-list-map": "~0.1.7",
+        "source-map": "~0.4.1"
+      },
+      "dependencies": {
+        "source-list-map": {
+          "version": "0.1.8",
+          "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz",
+          "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY="
+        },
+        "source-map": {
+          "version": "0.4.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
+    "webpack-dev-middleware": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz",
+      "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==",
+      "requires": {
+        "memory-fs": "^0.4.1",
+        "mime": "^2.4.4",
+        "mkdirp": "^0.5.1",
+        "range-parser": "^1.2.1",
+        "webpack-log": "^2.0.0"
+      },
+      "dependencies": {
+        "mime": {
+          "version": "2.4.4",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+          "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
+        }
+      }
+    },
+    "webpack-dev-server": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.9.0.tgz",
+      "integrity": "sha512-E6uQ4kRrTX9URN9s/lIbqTAztwEPdvzVrcmHE8EQ9YnuT9J8Es5Wrd8n9BKg1a0oZ5EgEke/EQFgUsp18dSTBw==",
+      "requires": {
+        "ansi-html": "0.0.7",
+        "bonjour": "^3.5.0",
+        "chokidar": "^2.1.8",
+        "compression": "^1.7.4",
+        "connect-history-api-fallback": "^1.6.0",
+        "debug": "^4.1.1",
+        "del": "^4.1.1",
+        "express": "^4.17.1",
+        "html-entities": "^1.2.1",
+        "http-proxy-middleware": "0.19.1",
+        "import-local": "^2.0.0",
+        "internal-ip": "^4.3.0",
+        "ip": "^1.1.5",
+        "is-absolute-url": "^3.0.3",
+        "killable": "^1.0.1",
+        "loglevel": "^1.6.4",
+        "opn": "^5.5.0",
+        "p-retry": "^3.0.1",
+        "portfinder": "^1.0.25",
+        "schema-utils": "^1.0.0",
+        "selfsigned": "^1.10.7",
+        "semver": "^6.3.0",
+        "serve-index": "^1.9.1",
+        "sockjs": "0.3.19",
+        "sockjs-client": "1.4.0",
+        "spdy": "^4.0.1",
+        "strip-ansi": "^3.0.1",
+        "supports-color": "^6.1.0",
+        "url": "^0.11.0",
+        "webpack-dev-middleware": "^3.7.2",
+        "webpack-log": "^2.0.0",
+        "ws": "^6.2.1",
+        "yargs": "12.0.5"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+        },
+        "ws": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+          "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+          "requires": {
+            "async-limiter": "~1.0.0"
+          }
+        }
+      }
+    },
+    "webpack-log": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
+      "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+      "requires": {
+        "ansi-colors": "^3.0.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "webpack-merge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
+      "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.15"
+      }
+    },
+    "webpack-sources": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+      "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+      "requires": {
+        "source-list-map": "^2.0.0",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
+    "webpack-subresource-integrity": {
+      "version": "1.1.0-rc.6",
+      "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz",
+      "integrity": "sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w==",
+      "requires": {
+        "webpack-core": "^0.6.8"
+      }
+    },
+    "websocket-driver": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
+      "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
+      "requires": {
+        "http-parser-js": ">=0.4.0 <0.4.11",
+        "safe-buffer": ">=5.1.0",
+        "websocket-extensions": ">=0.1.1"
+      }
+    },
+    "websocket-extensions": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+      "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg=="
+    },
+    "when": {
+      "version": "3.6.4",
+      "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz",
+      "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404="
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+    },
+    "wmf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="
+    },
+    "wordwrap": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+      "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+      "dev": true
+    },
+    "worker-farm": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+      "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+      "requires": {
+        "errno": "~0.1.7"
+      }
+    },
+    "worker-plugin": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-3.2.0.tgz",
+      "integrity": "sha512-W5nRkw7+HlbsEt3qRP6MczwDDISjiRj2GYt9+bpe8A2La00TmJdwzG5bpdMXhRt1qcWmwAvl1TiKaHRa+XDS9Q==",
+      "requires": {
+        "loader-utils": "^1.1.0"
+      }
+    },
+    "wrap-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+      "requires": {
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "ws": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
+      "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
+      "dev": true,
+      "requires": {
+        "async-limiter": "~1.0.0",
+        "safe-buffer": "~5.1.0",
+        "ultron": "~1.1.0"
+      }
+    },
+    "xlsx": {
+      "version": "0.15.6",
+      "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.15.6.tgz",
+      "integrity": "sha512-7vD9eutyLs65iDjNFimVN+gk/oDkfkCgpQUjdE82QgzJCrBHC4bGPH7fzKVyy0UPp3gyFVQTQEFJaWaAvZCShQ==",
+      "requires": {
+        "adler-32": "~1.2.0",
+        "cfb": "^1.1.4",
+        "codepage": "~1.14.0",
+        "commander": "~2.17.1",
+        "crc-32": "~1.2.0",
+        "exit-on-epipe": "~1.0.1",
+        "ssf": "~0.10.3",
+        "wmf": "~1.0.1"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.17.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
+        }
+      }
+    },
+    "xml2js": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+      "dev": true,
+      "requires": {
+        "sax": ">=0.6.0",
+        "xmlbuilder": "~11.0.0"
+      },
+      "dependencies": {
+        "sax": {
+          "version": "1.2.4",
+          "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+          "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+          "dev": true
+        }
+      }
+    },
+    "xmlbuilder": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+      "dev": true
+    },
+    "xmlhttprequest-ssl": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
+      "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
+      "dev": true
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+    },
+    "y18n": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+    },
+    "yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+    },
+    "yargs": {
+      "version": "12.0.5",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+      "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+      "requires": {
+        "cliui": "^4.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^1.0.1",
+        "os-locale": "^3.0.0",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^1.0.1",
+        "set-blocking": "^2.0.0",
+        "string-width": "^2.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^3.2.1 || ^4.0.0",
+        "yargs-parser": "^11.1.1"
+      }
+    },
+    "yargs-parser": {
+      "version": "11.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+      "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+      "requires": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    },
+    "yeast": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+      "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
+      "dev": true
+    },
+    "yn": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+      "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+      "dev": true
+    },
+    "zone.js": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz",
+      "integrity": "sha512-GkPiJL8jifSrKReKaTZ5jkhrMEgXbXYC+IPo1iquBjayRa0q86w3Dipjn8b415jpitMExe9lV8iTsv8tk3DGag=="
+    }
+  }
+}
diff --git a/mod2/ui/package.json b/mod2/ui/package.json
new file mode 100644 (file)
index 0000000..315ac39
--- /dev/null
@@ -0,0 +1,64 @@
+{
+    "name": "dcae-mod-fe",
+    "version": "0.0.0",
+    "scripts": {
+        "ng": "ng",
+        "start": "ng serve --proxy-config proxy.conf.json",
+        "build": "ng build",
+        "test": "ng test",
+        "lint": "ng lint",
+        "e2e": "ng e2e"
+    },
+    "private": true,
+    "dependencies": {
+        "@angular-devkit/build-angular": "^0.803.24",
+        "@angular/animations": "~8.0.0",
+        "@angular/cdk": "~8.2.3",
+        "@angular/common": "~8.0.0",
+        "@angular/compiler": "~8.0.0",
+        "@angular/core": "~8.0.0",
+        "@angular/forms": "~8.0.0",
+        "@angular/material": "^8.2.3",
+        "@angular/platform-browser": "~8.0.0",
+        "@angular/platform-browser-dynamic": "~8.0.0",
+        "@angular/router": "~8.0.0",
+        "@auth0/angular-jwt": "^4.0.0",
+        "@fortawesome/fontawesome-free": "^5.13.0",
+        "bootstrap": "^4.4.1",
+        "font-awesome": "^4.7.0",
+        "hammerjs": "^2.0.8",
+        "jszip": "^3.4.0",
+        "mat-table-exporter": "^9.0.0",
+        "ng4-loading-spinner": "^1.2.3",
+        "primeicons": "^2.0.0",
+        "primeng": "^8.0.0-rc.1",
+        "rxjs": "~6.4.0",
+        "tslib": "^1.9.0",
+        "xlsx": "^0.15.6",
+        "zone.js": "~0.9.1"
+    },
+    "devDependencies": {
+        "@angular-builders/custom-webpack": "^8.4.1",
+        "@angular-builders/dev-server": "^7.3.1",
+        "@angular/cli": "~8.0.1",
+        "@angular/compiler-cli": "~8.0.0",
+        "@angular/language-service": "~8.0.0",
+        "@babel/compat-data": "7.8.0",
+        "@types/jasmine": "~3.3.8",
+        "@types/jasminewd2": "~2.0.3",
+        "@types/node": "^8.9.5",
+        "codelyzer": "^5.0.0",
+        "dotenv": "^8.2.0",
+        "jasmine-core": "~3.4.0",
+        "jasmine-spec-reporter": "~4.2.1",
+        "karma": "~4.1.0",
+        "karma-chrome-launcher": "~2.2.0",
+        "karma-coverage-istanbul-reporter": "~2.0.1",
+        "karma-jasmine": "~2.0.1",
+        "karma-jasmine-html-reporter": "^1.4.0",
+        "protractor": "~5.4.0",
+        "ts-node": "~7.0.0",
+        "tslint": "~5.15.0",
+        "typescript": "~3.4.3"
+    }
+}
diff --git a/mod2/ui/pipelineConfig.json b/mod2/ui/pipelineConfig.json
new file mode 100644 (file)
index 0000000..2a1b18b
--- /dev/null
@@ -0,0 +1,21 @@
+{
+  "release": "3.1.3",
+  "blueprintRepo": "codecloud.web.att.com/scm/st_dcaecntr/platform_onboarding.git",
+  "featureBranch": "feature/cdptest",
+  "featureBranchDir": "DCAE-EOM-K8S/dcae-onboarding-dcae-mod-fe",
+  "jiraTag": "REPLACE-999",
+  "serviceInfo": [
+    {
+      "isKubernetes": true,
+      "serviceName": "dcae-mod-fe",
+      "compSpecFilePath": "public/dpo/comp_spec_toolbox_fe.json",
+      "inputsFilePath": "public/dpo/inputs_ftl3g_k8s.json",
+      "directory": ".",
+      "docker": {
+        "installDir": "/opt/app/dcae-onboarding-dcae-mod-fe",
+        "registry": "dockercentral.it.att.com:5100/com.att.dcae.dcaemod",
+        "filePath": "./Dockerfile"
+      }
+    }
+  ]
+}
diff --git a/mod2/ui/pom.xml b/mod2/ui/pom.xml
new file mode 100644 (file)
index 0000000..50122de
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ ============LICENSE_START=======================================================
+  ~  org.onap.dcae
+  ~  ================================================================================
+  ~  Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.oparent</groupId>
+        <artifactId>oparent</artifactId>
+        <version>2.0.0</version>
+    </parent>
+    <groupId>org.onap.dcaegen2.platform.mod</groupId>
+    <artifactId>ui</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.github.eirslett</groupId>
+                <artifactId>frontend-maven-plugin</artifactId>
+                <version>1.7.6</version>
+                <configuration>
+                    <workingDirectory>./</workingDirectory>
+                    <nodeVersion>v12.16.1</nodeVersion>
+                    <npmVersion>6.13.4</npmVersion>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>install node and npm</id>
+                        <goals>
+                            <goal>install-node-and-npm</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>npm install</id>
+                        <goals>
+                            <goal>npm</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>npm run build</id>
+                        <goals>
+                            <goal>npm</goal>
+                        </goals>
+                        <configuration>
+                            <arguments>run build</arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/mod2/ui/proxy.conf.json b/mod2/ui/proxy.conf.json
new file mode 100644 (file)
index 0000000..e730329
--- /dev/null
@@ -0,0 +1,11 @@
+{
+    "/api": {
+        "target": "http://zlecdyh2bdcc1s2dokr05.ec53e7.dyh2b.tci.att.com:32705/",
+        "secure": false,
+        "logLevel": "debug",
+        "pathRewrite": {
+            "^/api": ""
+        },
+        "changeOrigin": true
+    }
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/app-routing.module.ts b/mod2/ui/src/app/app-routing.module.ts
new file mode 100644 (file)
index 0000000..0ffe503
--- /dev/null
@@ -0,0 +1,55 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { NgModule } from '@angular/core';
+import { Routes, RouterModule, NavigationStart, NavigationEnd, Route} from '@angular/router';
+import { HomeComponent } from './home/home.component';
+import { OnboardingToolsComponent } from './onboarding-tools/onboarding-tools.component';
+import { CompSpecsComponent } from './comp-specs/comp-specs.component';
+import { MsInstancesComponent } from './msInstances/msInstances.component';
+import { LoginComponent } from './login/login.component';
+import { RegisterComponent } from './register/register.component';
+import { ResetPasswordComponent } from './reset-password/reset-password.component';
+import { AuthGuard } from './guards/auth.guard';
+import { RoleGuard } from './guards/role.guard';
+import { UserManagementComponent } from './user-management/user-management.component';
+import { LoginGuard } from './guards/login.guard';
+import { MicroservicesComponent } from './microservices/microservices.component';
+import { BlueprintsComponent } from './blueprints/blueprints.component';
+import { CompSpecValidationComponent } from './comp-spec-validation/comp-spec-validation.component';
+
+const routes: Routes = [
+  { path: '',  component: HomeComponent, canActivate:[LoginGuard]},
+  { path: 'home', component: HomeComponent, canActivate:[AuthGuard] },
+  { path: 'login', component: LoginComponent, canActivate:[LoginGuard]},
+  { path: 'register', component: RegisterComponent, canActivate:[AuthGuard, RoleGuard] },
+  { path: 'reset-password', component: ResetPasswordComponent, canActivate:[AuthGuard]},
+  { path: 'users', component: UserManagementComponent, canActivate:[AuthGuard, RoleGuard]},
+  { path: 'OnboardingTools', component: OnboardingToolsComponent, canActivate:[AuthGuard] },
+  { path: 'CompSpecs', component: CompSpecsComponent, canActivate:[AuthGuard]},
+  { path: 'ms-instances', component: MsInstancesComponent, canActivate:[AuthGuard] },
+  { path: 'base-microservices', component: MicroservicesComponent, canActivate: [AuthGuard] },
+  { path: 'blueprints', component: BlueprintsComponent, canActivate: [AuthGuard] },
+  { path: 'spec-validator', component: CompSpecValidationComponent, canActivate: [AuthGuard] }
+];
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes)],
+  exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/mod2/ui/src/app/app.component.css b/mod2/ui/src/app/app.component.css
new file mode 100644 (file)
index 0000000..0b9779a
--- /dev/null
@@ -0,0 +1,173 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.sidenav-container {
+  height: 100%;
+}
+
+.sidenav {
+  width: 190px;
+  background: linear-gradient(to bottom, #1D2329 0%, #454B52 8%)
+}
+
+.sidenav .mat-toolbar {
+  background: inherit;
+  color:rgb(225, 241, 247);
+}
+
+.subMenuItem{
+  color: white;
+  font-size: 14px;
+  font-weight: 400;
+  height: 6vh !important;
+  min-height: 35px !important;
+}
+
+.mat-toolbar.mat-primary {
+  display: block;
+  position: sticky;
+  top: 0;
+  z-index: 1;
+  height: 33px;
+  position: fixed;
+  border-radius: 0%;
+  background: linear-gradient(to bottom, #1D2329 0%, #454B52 135%)
+}
+
+.nav {
+  font-size: 16px;
+  font-weight: 500;
+  background-color: #454B52;
+  color:white;
+  height: 7vh !important;
+  min-height: 40px !important;
+}
+
+*:focus {
+    outline: none !important;
+    border: 0 !important;
+}
+
+.is-active{
+  background-color: #EA712F !important;
+}
+
+label {
+  cursor: pointer;
+}
+
+.input{
+  padding-top: 20px;
+}
+
+.inputLabel { 
+  font-weight: 600;
+  margin-left: 25px;
+  width: 160px;
+}
+
+.inputFieldSm { 
+  width: 200px; 
+  height: 35px; 
+  padding-left: 8px; 
+} 
+.inputFieldMed { 
+  width: 300px; 
+  height: 35px; 
+  padding-left: 6px; 
+} 
+.inputFieldLg { 
+  width: 400px; 
+  height: 35px; 
+  padding-left: 6px; 
+}
+
+/*  B R E A D C R U M B S .  .  .  .  .  .  .  .  */
+.breadcrumb { 
+  list-style: none; 
+  overflow: hidden; 
+  font: 12px Helvetica, Arial, Sans-Serif;
+  background: transparent;
+  margin-left: 5px;
+  margin-top: -3px;
+  padding: 0;
+}
+.breadcrumb li { 
+  float: left; 
+}
+.breadcrumb li a {
+  color: #1D2329;
+  font-weight: 700;
+  text-decoration: none; 
+  padding: 6px 0 6px 45px;
+  background: brown; /* fallback color */
+  background: hsla(21,82%,55%,1); 
+  position: relative; 
+  display: block;
+  float: left;
+}
+.breadcrumb li a:after { 
+  content: " "; 
+  display: block; 
+  width: 0; 
+  height: 0;
+  border-top: 50px solid transparent;           /* Go big on the size, and let overflow hide */
+  border-bottom: 50px solid transparent;
+  border-left: 30px solid hsla(21,82%,55%,1);
+  position: absolute;
+  top: 50%;
+  margin-top: -50px; 
+  left: 100%;
+  z-index: 2; 
+}      
+.breadcrumb li a:before { 
+  content: " "; 
+  display: block; 
+  width: 0; 
+  height: 0;
+  border-top: 50px solid transparent;           /* Go big on the size, and let overflow hide */
+  border-bottom: 50px solid transparent;
+  border-left: 30px solid #b9bec4;
+  position: absolute;
+  top: 50%;
+  margin-top: -50px; 
+  margin-left: 3px;
+  left: 100%;
+  z-index: 1; 
+}      
+.breadcrumb li:first-child a {
+  padding-left: 14px;
+}
+.breadcrumb li:nth-child(2) a       { background:        hsla(30,100%,50%,.95); }
+.breadcrumb li:nth-child(2) a:after { border-left-color: hsla(30,100%,50%,.95); }
+.breadcrumb li:nth-child(3) a       { background:        hsla(38,100%,50%,.90); }
+.breadcrumb li:nth-child(3) a:after { border-left-color: hsla(38,100%,50%,.90); }
+.breadcrumb li:nth-child(4) a       { background:        hsla(45,100%,50%,.85); }
+.breadcrumb li:nth-child(4) a:after { border-left-color: hsla(45,100%,50%,.85); }
+.breadcrumb li:nth-child(5) a       { background:        hsla(52,100%,50%,.80); }
+.breadcrumb li:nth-child(5) a:after { border-left-color: hsla(52,100%,50%,.80); }
+.breadcrumb li:last-child a {
+  background: transparent !important;
+  color: black;
+  pointer-events: none;
+  cursor: default;
+  margin-left: -10px;
+}
+.breadcrumb li:last-child a:after { border: 0; }
+.breadcrumb li a:hover { background: rgba(102, 102, 102, 0.993); color: #e9fffd; cursor: pointer}
+.breadcrumb li a:hover:after { border-left-color: hsla(0,0%,40%,1); color: #bbc9d8 !important; cursor: pointer}
diff --git a/mod2/ui/src/app/app.component.html b/mod2/ui/src/app/app.component.html
new file mode 100644 (file)
index 0000000..e214dc1
--- /dev/null
@@ -0,0 +1,136 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<ng4-loading-spinner></ng4-loading-spinner>
+<mat-sidenav-container style="background: linear-gradient(to top, #878C94 0%, #DCDFE3 10%, #F2F2F2 40%, #DCDFE3 80%, #878C94 110%);" class="sidenav-container" *ngIf="authService.authPass">
+    <mat-sidenav [class.mat-elevation-z2]="!isActive" [class.mat-elevation-z8]="isActive" #sideMenu class="sidenav"
+        fixedInViewport="true" [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'" 
+        [mode]="(isHandset$ | async) ? 'over' : 'side'" [opened]="(isHandset$ | async) === true">
+        <mat-toolbar></mat-toolbar>
+        <hr width=90%>
+        <mat-action-list style="margin-top: -10px;">
+            <button class="nav" mat-list-item (click)="setCrumbs('', 'reset')" [routerLink]="'/home'" [routerLinkActive]="'is-active'">Home</button>
+            <button class="nav" mat-list-item (click)="setCrumbs('User Management', 'reset')" *ngIf="authService.isAdmin" [routerLink]="'/users'" [routerLinkActive]="'is-active'">User Management</button>
+            <button class="nav" mat-list-item (click)="setCrumbs('Onboarding Tools', 'reset')" [routerLink]="'/OnboardingTools'" [skipLocationChange]="false" [routerLinkActive]="'is-active'">Onboarding Tools</button>
+            <button class="nav" mat-list-item (click)="msMenu()">
+                <i *ngIf="msMenuIconRight" class="pi pi-chevron-right"></i>
+                <i *ngIf="!msMenuIconRight" class="pi pi-chevron-down"></i>
+                &nbsp;&nbsp;Microservices</button>
+            <div *ngIf="showMsMenu" style="margin-left: 20px;">
+                <button class="subMenuItem" mat-list-item (click)="setCrumbs('Microservices', 'reset')" [routerLink]="'/base-microservices'" [routerLinkActive]="'is-active'">Microservices</button>
+                <button class="subMenuItem" mat-list-item (click)="setCrumbs('MS Instances', 'reset')"  [routerLink]="'/ms-instances'" [routerLinkActive]="'is-active'">MS Instances</button>
+                <button class="subMenuItem" mat-list-item (click)="setCrumbs('Blueprints', 'reset')"    [routerLink]="'/blueprints'" [routerLinkActive]="'is-active'">Blueprints</button>
+                
+                <button class="nav" mat-list-item (click)="utilitiesMenu()">
+                    <i *ngIf="utilitiesMenuIconRight" class="pi pi-chevron-right"></i>
+                    <i *ngIf="!utilitiesMenuIconRight" class="pi pi-chevron-down"></i>
+                    &nbsp;&nbsp;Utilites</button>
+
+                <div *ngIf="showUtilitiesMenu" style="margin-left: 20px;">
+                    <button class="subMenuItem" mat-list-item (click)="setCrumbs('Spec Validator', 'reset')"
+                        [routerLink]="'/spec-validator'" [routerLinkActive]="'is-active'">Spec Validator</button>
+                    <button class="subMenuItem" mat-list-item (click)="redirectToAPIs()">MOD APIs</button>
+                </div>
+            </div>
+                <button class="nav" mat-list-item (click)='redirectTo("https://wiki.web.att.com/pages/viewpage.action?spaceKey=DPD&title=DCAE+MOD+User+Guide")'>User Guide</button>
+
+        </mat-action-list>
+    </mat-sidenav>
+    <mat-sidenav-content style="margin-top: 70px; max-height: 90%">
+        <!-- Top bar when Logged in -->
+        <mat-toolbar [class.mat-elevation-z2]="!isActive" [class.mat-elevation-z8]="isActive" color="primary"
+                     fixedInViewport="true" style="background-color: #1D2329; color:#F2F2F2">
+            <button type="button" aria-label="Toggle sidenav" mat-icon-button (click)="sideMenu.toggle()" style="color:#F2F2F2">
+                <i *ngIf="!sideMenu.opened" class="pi pi-angle-double-right" style="font-size: 25px; margin-left: -30px; margin-bottom: 8px;"></i>
+                <i *ngIf="sideMenu.opened"  class="pi pi-angle-double-left"  style="font-size: 25px; margin-left: -30px; margin-bottom: 8px;"></i>
+            </button>
+            <span style="font-size: 22px; font-weight: 500; margin-left: 35px">MOD</span>
+            <button type="button" *ngIf="authService.authPass" (click)="handleLogout()"
+                    style="float:right; margin-right: 18%; height: 32px; border: none; color:#F2F2F2; background: linear-gradient(to bottom, #1D2329 0%, #454B52 135%);
+                           font-size: 16px; font-weight: 400">
+                <i class="pi pi-sign-out" style="vertical-align:text-top"></i>
+                Logout
+            </button>
+            <button type="button" *ngIf="authService.authPass" (click)="handleProfile()"
+                style="float:right; margin-right: 4%; height: 32px; border: none; color:#F2F2F2; background: linear-gradient(to bottom, #1D2329 0%, #454B52 135%);
+                    font-size: 16px; font-weight: 400">
+                    <i class="pi pi-user-edit" style="vertical-align:text-top"></i>
+                {{authService.getUser().username}}
+            </button>
+            <!-- BREADCRUMBS . . . . . -->
+            <ul class="breadcrumb">
+                <li *ngFor="let crumb of breadcrumbs" [routerLink]="crumb.link" (click)="setCrumbs(crumb.page, 'crumbClicked')"><a>{{crumb.page}}</a></li>
+            </ul>
+        </mat-toolbar>
+        <main [@routeAnimations]="prepareRoute(outlet)" class="content">
+            <router-outlet #outlet="outlet"></router-outlet>
+        </main>
+    </mat-sidenav-content>
+</mat-sidenav-container>
+
+<!-- Top bar when Logging in -->
+<mat-toolbar *ngIf="!authService.authPass" [class.mat-elevation-z2]="!isActive" [class.mat-elevation-z8]="isActive" color="primary"
+             fixedInViewport="true" style="background-color: #1D2329">
+    <span style="font-size: 22px; font-weight: 500">MOD</span>
+    <app-login></app-login>
+</mat-toolbar>
+
+<!-- Login Expired dialog -->
+<p-dialog header="Login Expired" [(visible)]="authService.reLoginMsg" [modal]="true"  [style]="{width:'25vw'}" [baseZIndex]="10000"
+        [draggable]="false" [resizable]="false">
+  <p>Your login has expired. Please log in again.</p>
+  <p-footer>
+    <button type="button" pButton icon="pi pi-check" (click)="onConfirm()" label="OK"></button>
+  </p-footer>
+</p-dialog>
+
+<!--reset User Password dialog-->
+<p-dialog [(visible)]="resetPasswordFlag" appendTo="body" [modal]="true" [transitionOptions]="'300ms'" [style]="{width: '630px'}" [baseZIndex]="10000"
+[closable]="true" (onHide)="closeResetDialog()">
+<p-header style="display: inline-flex;">
+    Reset User Password
+</p-header>
+<form [formGroup]="resetPasswordForm">
+    <!-- * * * New Password * * * -->
+    <div class="input">
+        <div class="ui-inputgroup" >
+            <label class="inputLabel">New Password</label>
+            <span class="ui-inputgroup-addon" (click)="hidePassword=!hidePassword">               
+            <i [ngClass]="hidePassword? 'pi pi-eye-slash':'pi pi-eye'"></i></span>
+            <input class="inputFieldSm" [type]="hidePassword? 'password':'text'" pInputText formControlName="password" />
+        </div>
+    </div>
+    <i *ngIf="resetPasswordForm.get('password').errors && resetPasswordForm.get('password').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">password should be more than 5 characters</i>
+    <!-- * * * Confirm New Password * * * -->
+    <div class="input">
+        <div class="ui-inputgroup" >
+        <label class="inputLabel">Confirm Password</label>
+        <span class="ui-inputgroup-addon" (click)="hideConfirmPassword=!hideConfirmPassword">               
+        <i [ngClass]="hideConfirmPassword? 'pi pi-eye-slash':'pi pi-eye'"></i></span>
+        <input class="inputFieldSm" [type]="hideConfirmPassword? 'password':'text'" pInputText formControlName="confirm_password" />
+        </div>
+    </div>
+    <i *ngIf="resetPasswordForm.errors && resetPasswordForm.errors.errMsg" style="width: 140px;margin-left: 20px;font-size: small;color: red;">{{resetPasswordForm.errors.errMsg}}</i>
+    <!-- * * * Submit and Cancel buttons * * * -->
+    <div style="margin-top: 2em; margin-left: 1.3em; margin-bottom: 2em;">
+        <button pButton type="button" (click)="closeResetDialog()" label="Cancel"></button>&nbsp;
+        <button pButton type="submit" (click)="resetPasswordForm.valid && submitReset()" class="ui-button-success" label="Submit" style="width: 70px"></button>
+    </div>
+</form>
+
+</p-dialog>
\ No newline at end of file
diff --git a/mod2/ui/src/app/app.component.spec.ts b/mod2/ui/src/app/app.component.spec.ts
new file mode 100644 (file)
index 0000000..d6eb43c
--- /dev/null
@@ -0,0 +1,90 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { TestBed, async } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { MatSidenavModule } from '@angular/material/sidenav';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatCardModule } from '@angular/material/card';
+import { MatTreeModule } from '@angular/material/tree';
+import { DialogModule } from 'primeng/dialog';
+import { MatListModule } from '@angular/material/list';
+import { LoginComponent } from './login/login.component';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { CardModule } from 'primeng/card';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { JwtHelperService, JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+describe('AppComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        RouterTestingModule,
+        Ng4LoadingSpinnerModule,
+        MatSidenavModule,
+        MatToolbarModule,
+        MatCardModule,
+        MatTreeModule,
+        DialogModule,
+        MatListModule,
+        FormsModule,
+        ReactiveFormsModule,
+        CardModule,
+        HttpClientTestingModule,
+        JwtModule,
+      ],
+      declarations: [
+        AppComponent,
+        LoginComponent
+      ], providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+
+  it('should create the app', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  });
+
+  it(`should have as title 'mod-fe'`, () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('mod-fe');
+  });
+
+  it(`should have menu item variables as 'false'`, () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.showUtilitiesMenu).toEqual(false);
+    expect(app.utilitiesMenuIconRight).toEqual(true);
+  });
+
+  it(`should change utilites menu arrow`, () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    app.utilitiesMenu()
+    expect(app.showUtilitiesMenu).toEqual(true);
+    expect(app.utilitiesMenuIconRight).toEqual(false);
+  });
+});
\ No newline at end of file
diff --git a/mod2/ui/src/app/app.component.ts b/mod2/ui/src/app/app.component.ts
new file mode 100644 (file)
index 0000000..87d29b4
--- /dev/null
@@ -0,0 +1,218 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, HostBinding, OnInit } from '@angular/core';
+import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
+import { Observable } from 'rxjs';
+import { map, shareReplay } from 'rxjs/operators';
+import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
+import { FlatTreeControl } from '@angular/cdk/tree';
+import { Event, Router, RouterOutlet, NavigationStart, NavigationEnd, RouterEvent } from '@angular/router';
+import { AuthService } from './services/auth.service';
+import { BreadcrumbService } from './services/breadcrumb.service';
+import { environment } from '../environments/environment';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { UserService } from './services/user.service';
+import { User } from './models/User';
+import { Authority } from './models/Authority.enum';
+
+interface TreeNode {
+  name: string;
+  children?: TreeNode[];
+}
+
+const TREE_DATA: TreeNode[] = [
+  {
+    name: 'Microservices',
+    children: [
+      { name: 'Microservices' },
+      { name: 'MS Instances' },
+      { name: 'Blueprints' },
+      { name: 'MOD APIs' }
+    ]
+  }
+];
+
+interface ExampleFlatNode {
+  expandable: boolean;
+  name: string;
+  level: number;
+}
+
+@Component({
+  selector: 'app-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.css']
+})
+export class AppComponent implements OnInit{
+
+  @HostBinding('@.disabled')
+  public animationsDisabled = true;
+  activeNode: any;
+  resetPasswordFlag: boolean = false;
+  resetPasswordForm: FormGroup;
+  hidePassword: boolean = true;
+  hideConfirmPassword: boolean = true;
+  breadcrumbs: any[] = [];
+
+  prepareRoute(outlet: RouterOutlet) {
+    return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
+  }
+
+  title = 'mod-fe';
+  right: boolean = true;
+  down: boolean = false;
+
+  menu_tree(){
+    this.right = !this.right;
+    this.down = !this.down;
+  }
+
+  isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
+    .pipe(
+      map(result => result.matches),
+      shareReplay()
+    );
+
+  redirectTo(link){
+    window.open(link, "_blank");
+  }
+
+  redirectToAPIs() {
+    window.open(`http://${environment.api_baseURL}:31001/swagger-ui.html#/`, "_blank");
+  }
+
+  constructor(private breakpointObserver: BreakpointObserver, private router: Router, public authService: AuthService,
+              private bread: BreadcrumbService, private fb: FormBuilder, private userService: UserService) { 
+    this.dataSource.data = TREE_DATA;     
+  }
+
+  ngOnInit() {
+    this.resetPasswordForm = this.fb.group(
+        {
+          password: ['', [Validators.minLength(6)]],
+          confirm_password: ''
+        },
+        {validators: [this.passwordValidator]}
+      );
+
+      //  Subscribe to breadcrumb changes
+      this.bread.breadcrumbs$.subscribe( (crumbArray) => {this.breadcrumbs = crumbArray});
+  }
+
+  setCrumbs(component, action) {
+    this.bread.setBreadcrumbs(component, action)
+  }
+
+  passwordValidator(group: FormGroup) {
+    if(group.value.password === group.value.confirm_password){
+      return null;
+    } else {
+      return {errMsg: 'Passwords do not match!'};
+    }
+  }
+
+  private _transformer = (node: TreeNode, level: number) => {
+    return {
+      expandable: !!node.children && node.children.length > 0,
+      name: node.name,
+      level: level,
+    };
+  }
+
+  tree_handler(name, treenode) {
+    if (name == "MOD APIs") {
+      window.open(`http://${environment.api_baseURL}:31001/swagger-ui.html#/`, "_blank");
+    } else if (name == "MS Instances") {
+      this.router.navigate(["ms-instances"]);
+      this.activeNode = treenode;
+    } else if(name == "Microservices") {
+      this.router.navigate(["base-microservices"]);
+      this.activeNode = treenode;
+    } else if(name == "Blueprints") {
+      this.router.navigate(["blueprints"]);
+      this.activeNode = treenode;
+    }
+    
+  }
+
+  treeControl = new FlatTreeControl<ExampleFlatNode>(
+    node => node.level, node => node.expandable);
+
+  treeFlattener = new MatTreeFlattener(
+    this._transformer, node => node.level, node => node.expandable, node => node.children);
+
+  dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
+
+  hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
+
+  showMsMenu = false;
+  msMenuIconRight = true;
+  msMenu() {
+    this.showMsMenu = !this.showMsMenu
+    this.msMenuIconRight = !this.msMenuIconRight
+  }
+
+  showUtilitiesMenu = false;
+  utilitiesMenuIconRight = true;
+  utilitiesMenu(){
+    this.showUtilitiesMenu = !this.showUtilitiesMenu
+    this.utilitiesMenuIconRight = !this.utilitiesMenuIconRight
+  }
+
+  onConfirm() {
+    this.authService.reLoginMsg = false;
+  }
+
+  handleLogout() {
+    this.showMsMenu = false
+    this.authService.logout()
+  }
+
+  handleProfile() {
+    console.log(this.authService.getUser().roles);
+    this.resetPasswordFlag = true;
+  }
+
+  closeResetDialog() {
+    this.resetPasswordForm.reset();
+    this.resetPasswordFlag = false;
+  }
+
+  submitReset() {
+    if(this.authService.getUser().roles.includes(Authority.ADMIN)){
+      this.userService.editUser(this.authService.getUser().username, this.resetPasswordForm.value as User).subscribe(res=>{
+        alert("Password reset successful. Please login in using the new credentials.");
+        this.authService.logout(); 
+    }, (err)=>{
+      alert(err.error.message);
+     
+    });
+    } else {
+      this.userService.editProfile(this.authService.getUser().username, this.resetPasswordForm.value as User).subscribe(res=>{
+        alert("Password reset successful. Please login in using the new credentials.");
+        this.authService.logout();
+      }, (err)=>{
+        alert(err.error.message);
+      });
+    }  
+     this.resetPasswordForm.reset();
+     this.resetPasswordFlag = false;
+  }
+
+}
diff --git a/mod2/ui/src/app/app.module.ts b/mod2/ui/src/app/app.module.ts
new file mode 100644 (file)
index 0000000..1e1da59
--- /dev/null
@@ -0,0 +1,172 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { PathLocationStrategy, LocationStrategy} from '@angular/common';
+
+import { SharedModule } from './shared/shared-module';
+import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+import { HomeComponent } from './home/home.component';
+import { LayoutModule } from '@angular/cdk/layout';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatButtonModule } from '@angular/material/button';
+import { MatSidenavModule } from '@angular/material/sidenav';
+import { MatIconModule } from '@angular/material/icon';
+import { MatListModule } from '@angular/material/list';
+import { MatTableModule } from '@angular/material/table';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTreeModule } from '@angular/material/tree';
+import { MatCardModule } from '@angular/material/card';
+import { MaterialElevationDirective } from './material-elevation.directive';
+import { MatProgressSpinnerModule, MatTooltipModule, MatMenuModule } from '@angular/material';
+import { OnboardingToolsComponent, SafePipe } from './onboarding-tools/onboarding-tools.component';
+import { CompSpecsComponent } from './comp-specs/comp-specs.component';
+import { MatTableExporterModule } from 'mat-table-exporter';
+import { TableModule } from 'primeng/table';
+import { MsInstancesComponent } from './msInstances/msInstances.component';
+import { ButtonModule } from 'primeng/button';
+import { SidebarModule } from 'primeng/sidebar';
+import { MenuModule } from 'primeng/menu';
+import { ToolbarModule } from 'primeng/toolbar';
+import { PanelMenuModule } from 'primeng/panelmenu';
+import { CardModule } from 'primeng/card';
+import { LoginComponent } from './login/login.component';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RegisterComponent } from './register/register.component';
+import { ResetPasswordComponent } from './reset-password/reset-password.component';
+import { AuthGuard } from './guards/auth.guard';
+import { RoleGuard } from './guards/role.guard';
+import { JwtInterceptorService } from './services/jwt-interceptor.service';
+import { JwtModule } from '@auth0/angular-jwt';
+import { UserManagementComponent } from './user-management/user-management.component';
+import { DialogModule } from 'primeng/dialog';
+import { ToastModule } from 'primeng/toast';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { PasswordModule } from 'primeng/password';
+import { TooltipModule } from 'primeng/tooltip';
+import { AccordionModule } from 'primeng/accordion';
+import { SplitButtonModule } from 'primeng/splitbutton';
+import { DropdownModule } from 'primeng/dropdown';
+import { FileUploadModule } from 'primeng/fileupload';
+import { InputTextareaModule } from 'primeng/inputtextarea';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { RadioButtonModule } from 'primeng/radiobutton';
+import { SelectButtonModule } from 'primeng/selectbutton';
+import { MessageService } from 'primeng/api';
+import { MsAddChangeComponent } from './ms-add-change/ms-add-change.component';
+import { MicroservicesComponent } from './microservices/microservices.component';
+import { PaginatorModule } from 'primeng/paginator';
+import { ScrollPanelModule } from 'primeng/scrollpanel'; 
+import { CalendarModule } from 'primeng/calendar';
+import { BlueprintsComponent } from './blueprints/blueprints.component';
+import { CompSpecAddComponent } from './comp-spec-add/comp-spec-add.component';
+import { MsInstanceAddComponent } from './ms-instance-add/ms-instance-add.component';
+import {MultiSelectModule} from 'primeng/multiselect';
+import {CheckboxModule} from 'primeng/checkbox';
+import { InputSwitchModule } from 'primeng/inputswitch';
+import { CompSpecValidationComponent } from './comp-spec-validation/comp-spec-validation.component';
+
+@NgModule({
+  declarations: [
+    AppComponent,
+    HomeComponent,
+    MaterialElevationDirective,
+    SafePipe,
+    OnboardingToolsComponent,
+    CompSpecsComponent,
+    MsInstancesComponent,
+    LoginComponent,
+    RegisterComponent,
+    ResetPasswordComponent,
+    UserManagementComponent,
+    MsAddChangeComponent,
+    MicroservicesComponent,
+    BlueprintsComponent,
+    CompSpecAddComponent,
+    MsInstanceAddComponent,
+    CompSpecValidationComponent
+  ],
+  imports: [
+    BrowserModule,
+    AppRoutingModule,
+    BrowserAnimationsModule,
+    SharedModule,
+    HttpClientModule,
+    LayoutModule,
+    MatToolbarModule,
+    MatButtonModule,
+    MatSidenavModule,
+    MatIconModule,
+    MatListModule,
+    MatTableModule,
+    MatPaginatorModule,
+    MatSortModule,
+    MatTreeModule,
+    MatCardModule,
+    MatProgressSpinnerModule,
+    MatTableExporterModule,
+    TableModule, 
+    ButtonModule,
+    SidebarModule,
+    MenuModule,
+    ToolbarModule,
+    PanelMenuModule,
+    FormsModule,
+    ReactiveFormsModule,
+    CardModule,
+    JwtModule.forRoot({
+      config: {
+        tokenGetter: ()=>localStorage.getItem('jwt')
+      }
+    }),
+    DialogModule,
+    ToastModule,
+    Ng4LoadingSpinnerModule,
+    TooltipModule,
+    AccordionModule, 
+    SplitButtonModule,
+    DropdownModule,
+    FileUploadModule,
+    InputTextareaModule,
+    MatExpansionModule,
+    PasswordModule,
+    RadioButtonModule,
+    SelectButtonModule,
+    MatTooltipModule,
+    PaginatorModule,
+    ScrollPanelModule,
+    MatMenuModule,
+    CalendarModule,
+    MultiSelectModule,
+    CheckboxModule,
+    InputSwitchModule
+  ],
+  providers: [AuthGuard, RoleGuard, {
+    provide: HTTP_INTERCEPTORS,
+    useClass: JwtInterceptorService,
+    multi: true
+  }, Location, { provide: LocationStrategy, useClass: PathLocationStrategy }, MessageService],
+  bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/mod2/ui/src/app/blueprints/blueprints.component.css b/mod2/ui/src/app/blueprints/blueprints.component.css
new file mode 100644 (file)
index 0000000..ccc03d9
--- /dev/null
@@ -0,0 +1,142 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+td{
+    word-break:break-all;
+    font-size: 12px;
+}
+
+th{
+    font-size: 12px;
+}
+
+.table_column_filter{
+    width: 100%; 
+    height: 20px; 
+    font-size: 10px;
+}
+
+.table_div{
+    margin: 0px 50px 10px 20px; 
+    min-width: 900px; 
+    width: 98%;
+    border: 1px solid darkslategray;
+}
+
+.fa-refresh{
+    cursor: pointer;
+}
+
+textarea  
+{  
+   font-size: 12px;   
+}
+
+.row-expand-layout{
+    display: grid; 
+    width: 100%;
+    grid-template-columns: 20% 30% auto 25%;
+    grid-gap: 10px;
+    grid-auto-rows: minmax(100px, auto);
+}
+
+.row-expand-card{
+    font-size: 12px;
+    border-radius: 5px;
+    border: 1px solid slategray;
+    padding: 10px 5px 5px 10px;
+    /* This height prevents vertical scroll bars in Notes and Failure Reason */
+    height: 92px;
+    overflow: hidden;
+}
+
+.table_export_buttons_alignment{
+    margin-left: 5px; 
+    margin-top: -32px; 
+    float: left;
+}
+
+.table_export_button{
+    border-radius: 5px; 
+    height: 22px; 
+    font-size: 14px; 
+    border: none; 
+    margin-top: 4px; 
+    margin-right: 7px;
+    display: inline-flex;
+}
+
+.table_caption_header{
+    margin-left: -18%; 
+    width: 82%; 
+    max-height: 25px; 
+    display: inline-flex;
+}
+
+.table_global_filter{
+    width: 250px;
+    height:25px; 
+    margin-bottom: -5px; 
+    font-size: 12px;
+    margin-left: 15px;
+}
+
+.table_title{
+    width: 40%; 
+    margin-left: 10%;
+}
+
+.table_action_item{
+    outline: none;
+    font-size: 12px;
+}
+
+::ng-deep .mat-menu-content {
+padding-top: 0px !important;
+padding-bottom: 0px !important;
+}
+.mat-menu-item{
+line-height:30px;
+height:30px;
+}
+
+.greenStatus{
+    background-color: rgba(80, 233, 105, 0.616)
+}
+
+.redStatus{
+    background-color: rgba(255, 29, 29, 0.527)
+}
+
+.blueStatus{
+    background-color: rgba(0, 183, 255, 0.384)
+}
+
+.greyStatus{
+    background-color: rgba(150, 150, 150, 0.432)
+}
+
+.ui-toast-detail{
+    white-space: pre-wrap;
+    font-size: 12px;
+}
+
+.ui-state-highlight {
+    background-color: #878C94 !important;
+    color: black !important;
+}
diff --git a/mod2/ui/src/app/blueprints/blueprints.component.html b/mod2/ui/src/app/blueprints/blueprints.component.html
new file mode 100644 (file)
index 0000000..53fed09
--- /dev/null
@@ -0,0 +1,283 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<ng4-loading-spinner [timeout]="1000000"></ng4-loading-spinner>
+<div class="table_div" [style.visibility]="visible">
+    <!-- * * * * Table of Blueprints * * * * -->
+    <p-table #dt [columns]="cols" [(selection)]="selectedBPs" [value]="bpElements" sortMode="multiple" [paginator]="true"
+        [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" (onFilter)="onTableFiltered(dt.filteredValue, $event)" dataKey="id" editMode="row">
+
+        <!-- * * * * Top caption row * * * * -->
+        <ng-template pTemplate="caption">
+
+            <div class="table_caption_header">
+                <!--Blueprints Table Header-->
+                <div>
+                    <!-- * * * * Refresh * * * * -->
+                    <i class="fa fa-refresh" (click)="getAllBPs()"></i>
+                    <!-- * * * * Global filter * * * * -->
+                    <input class="table_global_filter" type="text" pInputText size="50" placeholder="Global Filter"
+                        (input)="dt.filterGlobal($event.target.value, 'contains')">
+                    <i class="fa fa-search" style="margin:4px 0 0 8px"></i>
+                </div>
+
+                <h4 class="table_title"><b>Deployment Artifacts</b></h4>
+
+            </div>
+        </ng-template>
+
+        <!-- * * * * Header row with dynamic column names. Columns include microservice Name, Release, Tag, Type, Version and Status  * * * * -->
+        <ng-template pTemplate="header" let-columns>
+            <tr style="text-align: center; vertical-align: bottom;">
+                <th style="width: 3em"></th>
+                <th class="ui-state-highlight" *ngFor="let col of columns" style="outline: none;" [pSortableColumn]="col.field" style="font-size: 12px; outline: none;"
+                    [ngStyle]="{'width': col.width}">
+                    {{col.header}}<br>
+                    <p-sortIcon [field]="col.field"></p-sortIcon>
+                </th>
+                <th style="width: 7%; vertical-align: middle;">
+                    Actions
+                </th>
+            </tr>
+
+            <!-- * * * * Second header row for individual column filters * * * * -->
+            <tr>
+                <th style="width: 3em"></th>
+                <!-- * * * * column filters * * * * -->
+                <th *ngFor="let col of columns" style="text-align: center;" [ngSwitch]="col.field">
+                    <input *ngSwitchCase="'instanceName'" [(ngModel)]="filteredName" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                    <input *ngSwitchCase="'instanceRelease'" [(ngModel)]="filteredRelease" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                    <input *ngSwitchCase="'tag'" [(ngModel)]="filteredTag" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                    <input *ngSwitchCase="'type'" [(ngModel)]="filteredType" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                    <input *ngSwitchCase="'version'" [(ngModel)]="filteredVersion" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                    <input *ngSwitchCase="'status'" [(ngModel)]="filteredStatus" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                </th>
+                <th>
+                    <div style="text-align: center;">
+                        <p-tableHeaderCheckbox style="padding-right: 5px;"></p-tableHeaderCheckbox>
+                        <button pButton type="button" class="ui-button-secondary" (click)="enableButtonCheck()" [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger" 
+                            style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;">
+                            <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+                        </button>
+                        <mat-menu #menu="matMenu" xPosition="before">
+                            <!--<div (mouseleave)="menuTrigger.closeMenu()">-->
+
+                                <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                    <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-download"></i> Download</span>
+                                </div>
+
+                                <!-- * * * * Download Blueprints * * * * -->
+                                <div matTooltip="No Blueprints Selected" [matTooltipDisabled]="canDownload" matTooltipPosition="left">
+                                    <button mat-menu-item class="table_action_item" (click)="downloadSelectedBps()" [disabled]="!canDownload">Download Selected Blueprints</button>
+                                </div>
+
+                                <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                    <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-times"></i> Delete</span>
+                                </div>
+
+                                <!-- * * * * Delete Selected Blueprints * * * -->
+                                <div [matTooltip]="deleteTooltip" [matTooltipDisabled]="canDelete" matTooltipPosition="left">
+                                    <button mat-menu-item  (click)="warnDeleteBlueprint(null)" class="table_action_item" [disabled]="!canDelete">Delete Selected Blueprints</button>
+                                </div>
+                                
+                                <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                    <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+                                </div>
+
+                                <!-- * * * * State Changes * * * * -->
+                                <div matTooltip="No Blueprints Selected" [matTooltipDisabled]="canUpdate" matTooltipPosition="left">
+                                    <button *ngFor="let state of states" mat-menu-item class="table_action_item" (click)="updateSelectedStatusesCheck(state)" [disabled]="!canUpdate">{{state.label}}</button>
+                                </div>
+
+                            <!--</div>-->
+                        </mat-menu>
+                    </div>
+                </th>
+            </tr>
+        </ng-template>
+
+        <!-- * * * * dynamic rows generated from columns object and msElems object * * * * -->
+        <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-bpElem>
+            <tr>
+                <!-- * * * * Column for row expand buttons * * * * -->
+                <td>
+                    <a href="#" [pRowToggler]="rowData">
+                        <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+                    </a>
+                </td>
+
+                <td *ngFor="let col of cols">
+                    <div *ngIf="col.field==='status'" style="width: -moz-max-content; width: fit-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;" 
+                        [ngClass]="{
+                            'greenStatus' : bpElem[col.field] === 'DEV_COMPLETE' || bpElem[col.field] === 'PST_CERTIFIED' || bpElem[col.field] === 'ETE_CERTIFIED' || bpElem[col.field] === 'IN_PROD',
+                            'redStatus' : bpElem[col.field] === 'PST_FAILED' || bpElem[col.field] === 'ETE_FAILED' || bpElem[col.field] === 'PROD_FAILED',
+                            'blueStatus' : bpElem[col.field] === 'IN_DEV' || bpElem[col.field] === 'IN_PST' || bpElem[col.field] === 'IN_ETE',
+                            'greyStatus' : bpElem[col.field] === 'NOT_NEEDED'}">
+                        {{bpElem[col.field]}}
+                    </div>
+                    <div *ngIf="col.field!=='status'">{{bpElem[col.field]}}</div>
+                </td>
+
+                <!-- * * * * Actions Column * * * * -->
+                <td>
+                    <div style="text-align: center;">
+                        <p-tableCheckbox [value]="rowData" style="padding-right: 5px;"></p-tableCheckbox>
+                        <!-- * * * * Actions Button * * * * -->
+                        <button #actionButton pButton type="button" #menuTrigger="matMenuTrigger" class="ui-button-secondary" style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;" [matMenuTriggerFor]="menu">
+                            <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+                        </button>
+                        <!-- * * * * Actions Menu Items * * * * -->
+                        <mat-menu #menu="matMenu">
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i style="font-size: 12px;" class="pi pi-search"></i> View</span>
+                            </div>
+                            
+                            <button mat-menu-item class="table_action_item" (click)="viewBpContent(rowData)">View BP Content</button>
+                                
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-times"></i> Delete</span>
+                            </div>
+
+                            <div matTooltip='Only blueprints that are in a status of "In Dev", "Not Needed" or "Dev Complete" can be deleted' [matTooltipDisabled]="rowData.status === 'IN_DEV' || rowData.status === 'NOT_NEEDED' || rowData.status === 'DEV_COMPLETE'" matTooltipPosition="left">
+                                <button mat-menu-item class="table_action_item" (click)="warnDeleteBlueprint(rowData)" [disabled]="rowData.status !== 'IN_DEV' && rowData.status !== 'NOT_NEEDED' && rowData.status !== 'DEV_COMPLETE'">Delete Blueprint</button>
+                            </div>
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+                            </div>
+
+                            <div>
+                                <div *ngFor="let state of states">
+                                    <button *ngIf="rowData.status !== state.field" mat-menu-item class="table_action_item" (click)="updateState(state, rowData, false)">{{state.label}}</button>
+                                </div>
+                            </div>
+                        </mat-menu>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+        
+
+        <!-- * * * * Row expand content * * * * -->
+        <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+            <tr>
+                <td [attr.colspan]="columns.length + 2">
+                    <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+                        <!-- * * * * Audit Fields * * * * -->
+                        <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295);">
+                            <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+                            <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+                            <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+                            <b>Updated On:</b> {{rowData.metadata.updatedOn}}<br>
+                        </div>
+                        <!-- * * * * Notes * * * * -->
+                        <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.295); white-space: pre-line;">
+                            <b>Notes:</b><br>
+                            <p-scrollPanel [style]="{width: '100%', height: '75px'}">
+                                <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+                            </p-scrollPanel>
+                        </div>
+                        <!-- * * * * Labels * * * * -->
+                        <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.295)">
+                            <b style="padding-bottom: 5px;">Labels:</b><br>
+                            <div *ngFor="let label of rowData['metadata']['labels']"
+                                style="display: inline-flex; margin-top: 5px;">
+                                <div style="padding: 2px 7px 3px 0px;">
+                                    <span style="background-color: rgba(80, 80, 80, 0.185); padding: 3px; border-radius: 3px;">{{label}}</span>
+                                </div>
+                            </div>
+                        </div>
+                        <!-- * * * * Failure Reason * * * * -->
+                        <div class="row-expand-card" style="background-color: rgba(225, 65, 65, 0.295)">
+                            <b>Failure Reason:</b><br>
+                            <p-scrollPanel [style]="{width: '100%', height: '75px'}">
+                                <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.failureReason}}</div>
+                            </p-scrollPanel>
+                        </div>
+
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+    </p-table>
+
+    <!-- * * * * download buttons for exporting table to either csv or excel file * * * * -->
+    <div class="table_export_buttons_alignment">
+        <button pButton type="button" (click)="exportTable('csv')" matTooltip="Export Table to CSV" matTooltipPosition="above" class="table_export_button" style="width: 55px;">
+            <i class="pi pi-file" style="margin-top: 3px; margin-left: 4px;"></i>
+            <label style="font-weight: 800; margin-top: 1px;">CSV</label>
+        </button>
+        <button pButton type="button" (click)="exportTable('excel')" matTooltip="Export Table to XLSX" class="table_export_button" matTooltipPosition="above" style="width: 65px; background-color: green;">
+            <i class="pi pi-file-excel" style="margin-top: 3px; margin-left: 4px;"></i>
+            <label style="font-weight: 800; margin-top: 1px">Excel</label>
+        </button>
+    </div>
+</div>
+
+<p-toast key="statusUpdate" class="ui-toast-detail" [style]="{width: '500px'}">
+    <ng-template let-message pTemplate="message">
+        <p><b>{{message.summary}}</b></p>
+        <p style="font-size: 12px;">{{message.detail}}</p>
+    </ng-template>
+</p-toast>
+<p-toast key="multipleBpReleasesSelected"></p-toast>
+
+<p-toast key="bpDeleteResponse"></p-toast>
+
+
+<!-- * * * * Confirm multiple statuses/releases update toast * * * * -->
+<p-toast position="center" key="confirmToast" (onClose)="onReject()" [baseZIndex]="5000" [style]="{width: '300px'}">
+    <ng-template let-message pTemplate="message">
+        <div style="text-align: center">
+            <i class="pi pi-exclamation-triangle" style="font-size: 3em"></i>
+            <h3>{{message.summary}}</h3>
+            <p>{{message.detail}}</p>
+        </div>
+        <div style="width: 100%; text-align: center;">
+            <button type="button" pButton (click)="onConfirm()" label="Confirm" class="ui-button-success"></button>
+            <button type="button" pButton (click)="onReject()" label="Cancel" class="ui-button-secondary" style="margin-left: 20px;"></button>
+        </div>
+    </ng-template>
+</p-toast>
+
+<!-- * * * * Confirm delete blueprint * * * * -->
+<p-toast position="center" key="confirmDeleteToast" class="ui-toast-detail" (onClose)="onReject()" [baseZIndex]="5000" [style]="{width: '300px'}">
+    <ng-template let-message pTemplate="message">
+        <div style="text-align: center">
+            <i class="pi pi-exclamation-triangle" style="font-size: 3em"></i>
+            <h3>{{message.summary}}</h3>
+            Confirm to delete blueprint(s):<br><br>
+            <p style="text-align: left; margin-left: 10%;">{{message.detail}}</p><br>
+        </div>
+        <div style="width: 100%; text-align: center;">
+            <button type="button" pButton (click)="onConfirmDelete()" label="Confirm" class="ui-button-success"></button>
+            <button type="button" pButton (click)="onRejectDelete()" label="Cancel" class="ui-button-secondary"
+                style="margin-left: 20px;"></button>
+        </div>
+    </ng-template>
+</p-toast>
+
+<!-- * * * * View BP Content Pop Up * * * * -->
+<p-dialog [(visible)]="showBpContentDialog" header="Blueprint Content" appendTo="body" [maximizable]="true" [modal]="true" [style]="{width: '80vw'}" [baseZIndex]="10000"
+    [closable]="false">
+    <pre>{{BpContentToView}}</pre>
+    <p-footer>
+        <button pButton label="Close" (click)="showBpContentDialog=false" type="button"></button>
+        <button pButton label="Download" (click)="download()" type="button"></button>
+    </p-footer>
+</p-dialog>
\ No newline at end of file
diff --git a/mod2/ui/src/app/blueprints/blueprints.component.spec.ts b/mod2/ui/src/app/blueprints/blueprints.component.spec.ts
new file mode 100644 (file)
index 0000000..caa5c38
--- /dev/null
@@ -0,0 +1,153 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatMenuModule, MatTooltipModule } from '@angular/material';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ScrollPanelModule } from 'primeng/scrollpanel';
+import { TableModule } from 'primeng/table';
+import { ToastModule } from 'primeng/toast';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import { BlueprintsComponent } from './blueprints.component';
+
+describe('BlueprintsComponent', () => {
+  let component: BlueprintsComponent;
+  let fixture: ComponentFixture<BlueprintsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [BlueprintsComponent],
+      imports: [
+        Ng4LoadingSpinnerModule,
+        TableModule,
+        MatMenuModule,
+        ScrollPanelModule,
+        ToastModule,
+        DialogModule,
+        DropdownModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        HttpClientTestingModule,
+        ToastModule,
+        RouterTestingModule,
+        MatTooltipModule,
+        BrowserAnimationsModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(BlueprintsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it(`should set states`, async(() => {
+    const fixture = TestBed.createComponent(BlueprintsComponent);
+    const app = fixture.debugElement.componentInstance;
+    let mockStates = [
+      'state1', 
+      'state2'
+    ]
+    app.setMenuStates(mockStates)
+    fixture.detectChanges();
+    expect(app.states).toEqual([ ]);
+  }));
+
+  it(`should not enable action buttons`, async(() => {
+    const fixture = TestBed.createComponent(BlueprintsComponent);
+    const app = fixture.debugElement.componentInstance;
+    
+    app.selectedBPs = []
+    app.enableButtonCheck()
+    fixture.detectChanges();
+
+    expect(app.canDownload).toEqual(false);
+    expect(app.canUpdate).toEqual(false);
+    expect(app.canDelete).toEqual(false);
+  }));
+
+  it(`should enable download/update buttons but not delete`, async(() => {
+    const fixture = TestBed.createComponent(BlueprintsComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    app.selectedBPs = [{status: 'TEST'}]
+    app.enableButtonCheck()
+    fixture.detectChanges();
+
+    expect(app.canDownload).toEqual(true);
+    expect(app.canUpdate).toEqual(true);
+    expect(app.canDelete).toEqual(false);
+  }));
+
+  it(`should enable download/update buttons but not delete`, async(() => {
+    const fixture = TestBed.createComponent(BlueprintsComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    app.selectedBPs = [{ status: 'IN_DEV' }]
+    app.enableButtonCheck()
+    fixture.detectChanges();
+
+    expect(app.canDownload).toEqual(true);
+    expect(app.canUpdate).toEqual(true);
+    expect(app.canDelete).toEqual(true);
+  }));
+
+  it(`should enable download/update buttons but not delete`, async(() => {
+    const fixture = TestBed.createComponent(BlueprintsComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockBpToView = {
+      tag: 'test-tag',
+      type: 'k8s',
+      instanceRelease: '2008',
+      version: '1',
+      content: 'test'
+    }
+
+    app.viewBpContent(mockBpToView)
+    fixture.detectChanges();
+
+    expect(app.BpFileNameForDownload).toEqual('test-tag_k8s_2008_1');
+    expect(app.BpContentToView).toEqual('test');
+    expect(app.showBpContentDialog).toEqual(true);
+  }));
+  
+});
+
+
diff --git a/mod2/ui/src/app/blueprints/blueprints.component.ts b/mod2/ui/src/app/blueprints/blueprints.component.ts
new file mode 100644 (file)
index 0000000..b4a7e73
--- /dev/null
@@ -0,0 +1,602 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, ViewChild, ElementRef, Input, EventEmitter, Output, ChangeDetectorRef } from '@angular/core';
+import { Table } from 'primeng/table';
+import { MessageService } from 'primeng/api';
+import { trigger, state, style, transition, animate } from '@angular/animations';
+import * as saveAs from 'file-saver';
+import * as JSZip from 'jszip';
+import { AuthService } from '../services/auth.service';
+import { DatePipe } from '@angular/common';
+import { DeploymentArtifactService } from '../services/deployment-artifact.service';
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+import { Toast } from 'primeng/toast'
+import { ActivatedRoute } from '@angular/router';
+import { DownloadService } from '../services/download.service';
+
+@Component({
+  selector: 'app-blueprints',
+  templateUrl: './blueprints.component.html',
+  styleUrls: ['./blueprints.component.css'],
+  animations: [
+    trigger('rowExpansionTrigger', [
+      state('void', style({
+        transform: 'translateX(-10%)',
+        opacity: 0
+      })),
+      state('active', style({
+        transform: 'translateX(0)',
+        opacity: 1
+      })),
+      transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+    ])
+  ],
+  providers: [DatePipe, MessageService]
+})
+export class BlueprintsComponent implements OnInit {
+  @ViewChild(Table, { static: false }) dt: Table;
+  @ViewChild(Toast, { static: false }) toast: Toast;
+
+  /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. **/
+  bpElements: BlueprintElement[] = [];
+  cols: any[] = [
+    { field: 'instanceName', header: 'Instance Name' },
+    { field: 'instanceRelease', header: 'Instance Release', width: '7%' },
+    { field: 'tag', header: 'Tag' },
+    { field: 'type', header: 'Type', width: '7%' },
+    { field: 'version', header: 'Version', width: '6%' },
+    { field: 'status', header: 'Status', width: '125px' }];
+  states: {field: string, label: string}[] = [];
+  columns: any[];
+  filteredRows: any;
+  downloadItems: { label: string; command: () => void; }[];
+  username: string;
+  showBpContentDialog: boolean = false;
+  selectedBPs: BlueprintElement[] = [];
+  //  Hides the BP list until the rows are retrieved and filtered
+  visible = "hidden";
+  // These 2 fields are passed from MS Instance to filter the BP list
+  tag: string;
+  release: string;
+
+  filteredName:    string;
+  filteredRelease: string;
+  filteredTag:     string;
+  filteredType:    string;
+  filteredVersion: string;
+  filteredStatus:  string;
+
+  constructor(private change: ChangeDetectorRef, private messageService: MessageService, private authService: AuthService,
+              private datePipe: DatePipe, private bpApis: DeploymentArtifactService, private spinnerService: Ng4LoadingSpinnerService,
+              private route: ActivatedRoute, private downloadService: DownloadService) { }
+
+  ngOnInit() {
+    
+    this.username = this.authService.getUser().username;
+    
+    this.getStates();
+    this.getAllBPs();
+
+    this.change.markForCheck();
+
+    this.route.queryParams.subscribe((params) => {
+      this.filteredTag     = params['tag'];
+      this.filteredRelease = params['release']});
+  }
+
+  //gets statuses for status updates
+  getStates(){
+    this.states = []
+    this.bpApis.getStatuses().subscribe((response) => {this.setMenuStates(response)})
+  }
+
+  //fills actions menu with states
+  setMenuStates(states){
+    for(let item of states){
+      this.states.push({
+        field: item,
+        label: 'To  ' + item
+      })
+    }
+  }
+
+  canDelete: boolean = false;
+  canDownload: boolean = false;
+  canUpdate: boolean = false;
+  deleteTooltip: string;
+  enableButtonCheck(){
+    if(this.selectedBPs.length > 0){
+      this.canDownload = true;
+      this.canUpdate = true;
+      
+      for(let item of this.selectedBPs){
+        if (item.status !== 'IN_DEV' && item.status !== 'NOT_NEEDED' && item.status !== 'DEV_COMPLETE'){
+          this.canDelete = false;
+          this.deleteTooltip = 'Only blueprints that are in a status of "In Dev", "Not Needed" or "Dev Complete" can be deleted'
+          break
+        } else {
+          this.canDelete = true;
+        }
+      }
+
+    } else {
+      this.canDownload = false;
+      this.canUpdate = false;
+      this.canDelete = false;
+      this.deleteTooltip = 'No Blueprints Selected'
+    }
+  }
+
+  updateStateTo: string = ''; //selected state to update blueprint to
+  //checks if there are different releases/statuses selected
+  updateSelectedStatusesCheck(state){
+    this.updateStateTo = state.field
+    let multipleStates: boolean = false
+    let multipleReleases: boolean = false
+    let firstStatus = this.selectedBPs[0]['status']
+    let firstRelease = this.selectedBPs[0]['instanceRelease']
+
+    for(let bp of this.selectedBPs){
+      if(bp.instanceRelease !== firstRelease){
+        multipleReleases = true
+      }
+      if (bp.status !== firstStatus) {
+        multipleStates = true
+      }
+    }
+
+    if(multipleReleases && multipleStates){
+      this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different releases and statuses. Confirm to proceed.' });
+    } else if (multipleReleases && !multipleStates) {
+      this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different releases. Confirm to proceed.' });
+    } else if (!multipleReleases && multipleStates) {
+      this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different statuses. Confirm to proceed.' });
+    } else if (!multipleReleases && !multipleStates){
+      this.updateSelectedStatuses()
+    }
+  }
+  onConfirm() {
+    this.messageService.clear('confirmToast')
+    this.updateSelectedStatuses()
+  }
+  onReject() {
+    this.messageService.clear('confirmToast')
+  }
+
+  /* * * * Update status for multiple blueprints * * * */
+  successfulStatusUpdates: number = 0 //keeps track of how many status updates were successful
+  selectionLength: number = 0 //length of array of blueprints with different statuses than update choice
+  statusUpdateCount: number = 0 //keeps track of how many api calls have been made throughout a loop
+  statusUpdateErrors: string[] = [] //keeps list of errors
+  updateSelectedStatuses(){
+    this.successfulStatusUpdates = 0      
+    this.statusUpdateErrors = []          
+    this.statusUpdateCount = 0
+
+    let bpsToUpdate = this.selectedBPs.filter(bp => bp.status !== this.updateStateTo) //array of blueprints with different statuses than update choice
+    this.selectionLength = bpsToUpdate.length;
+
+    if (this.selectionLength === 0) { this.selectedBPs = [] } else {
+      this.spinnerService.show();
+      this.updateState(this.updateStateTo, bpsToUpdate, true)
+    }
+  }
+
+  /* * * * Update Statuses * * * */
+  //state is the state to update to
+  //data is the bp data from selection
+  //multiple is whether updates were called for single blueprint or multiple selected blueprints
+  updateState(state, data, multiple){
+    //single status update
+    if(!multiple){
+      this.bpApis.patchBlueprintStatus(state.field, data['id']).subscribe(
+        (response: string) => {
+          data.status = state.field
+          this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: 'Status Updated' });
+        }, errResponse => {
+          this.statusUpdatesResponseHandler(errResponse, false)
+        }
+      )
+    } 
+    
+    //multiple status updates
+    if(multiple){
+      (async () => {
+        for (let bp of data) {
+          this.bpApis.patchBlueprintStatus(this.updateStateTo, bp.id).subscribe(
+            (response: string) => {
+              bp.status = this.updateStateTo
+              this.statusUpdatesResponseHandler(null, true)
+            }, errResponse => {
+              this.statusUpdatesResponseHandler(errResponse, true)
+            }
+          )
+          await timeout(1500);
+        }
+      })();
+
+      function timeout(ms) {
+        return new Promise(resolve => setTimeout(resolve, ms));
+      }
+    }
+  }
+
+  /* * * * Handles errors and messages for status updates * * * */
+  statusUpdatesResponseHandler(response, multiple){
+    if(!multiple){
+      if(response !== null){
+        if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) {
+          let message = response.error.message.replace('Only 1 blueprint can be in the DEV_COMPLETE state.  ', '\n\nOnly 1 blueprint can be in the DEV_COMPLETE state.\n')
+          this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Status Not Updated', detail: message, sticky: true });
+        } else {
+          this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true });
+        }
+      }
+    }
+
+    if(multiple){
+      this.statusUpdateCount++
+      if (response === null) {
+        this.successfulStatusUpdates++
+      } else {
+        if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) {
+          let error = response.error.message.split('Only 1 blueprint can be in the DEV_COMPLETE state.')[0]
+          this.statusUpdateErrors.push(error)
+        } else { 
+          this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true });
+        }
+      }
+
+      if (this.statusUpdateCount === this.selectionLength) {
+        if (this.successfulStatusUpdates > 0) {
+          this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: `(${this.successfulStatusUpdates} of ${this.selectionLength}) Statuses Updated`, life: 5000 });
+        }
+        if (this.statusUpdateErrors.length > 0) {
+          let message: string = ''
+          for (let elem of this.statusUpdateErrors) {
+            message += '- ' + elem + '\n'
+          }
+          message += '\nOnly 1 blueprint can be in the DEV_COMPLETE state.\nChange the current DEV_COMPLETE blueprint to NOT_NEEDED or IN_DEV before changing another to DEV_COMPLETE.'
+          this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Statuses Not Updated', detail: message, sticky: true });
+        }
+        this.spinnerService.hide()
+        this.selectedBPs = []
+      }
+    }
+  }
+
+  bpToDelete: any;
+  deleteSingle: boolean = false;
+  rowIndexToDelete;
+  rowIndexToDeleteFiltered;
+  warnDeleteBlueprint(data){
+    if(data !== null){
+      this.deleteSingle = true;
+      this.rowIndexToDeleteFiltered = this.filteredRows.map(function (x) { return x.id; }).indexOf(data['id']);
+      this.rowIndexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(data['id']);
+      this.bpToDelete = data;
+      this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: `- ${data.instanceName} (v${data.version}) for ${data.instanceRelease}` });
+    } else {
+      this.deleteSingle = false;
+      this.selectionLength = this.selectedBPs.length;
+      let warnMessage: string = ''
+      for(let item of this.selectedBPs){
+        warnMessage += `- ${item.instanceName} (v${item.version}) for ${item.instanceRelease}\n`
+      }
+      this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: warnMessage });
+    }    
+  }
+
+  resetFilter = false;
+  onConfirmDelete() {
+    this.messageService.clear('confirmDeleteToast')
+
+    if (this.filteredName !== '' || this.filteredRelease !== '' || this.filteredTag !== '' || this.filteredType !== '' || this.filteredVersion !== '' || this.filteredStatus !== ''){
+      this.resetFilter = true;
+    } else {this.resetFilter = false}
+    
+    if(this.deleteSingle){
+      this.bpApis.deleteBlueprint(this.bpToDelete['id']).subscribe(response => {
+        this.checkBpWasSelected(this.bpToDelete['id'])
+        this.bpElements.splice(this.rowIndexToDelete, 1)
+        if (this.resetFilter) {
+          this.resetFilters()
+        }
+        this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifact Deleted' });
+      }, error => {
+        this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message });
+      })
+    } else {
+      for(let item of this.selectedBPs){
+        this.bpApis.deleteBlueprint(item.id).subscribe(response => {
+          this.deleteResponseHandler(true, item.id)
+        }, error => {
+          this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message });
+        })
+      }
+    }
+  }
+  onRejectDelete() {
+    this.messageService.clear('confirmDeleteToast')
+  }
+
+  checkBpWasSelected(id){
+    if(this.selectedBPs.length > 0){
+      for(let item of this.selectedBPs){
+        if(item.id === id){
+          let indexToDelete = this.selectedBPs.map(function (x) { return x.id; }).indexOf(item['id']);
+          this.selectedBPs.splice(indexToDelete, 1)
+        }
+      }
+    }
+  }
+
+  bpsToDelete: string[] = [];
+  deleteBpCount = 0;
+  deleteResponseHandler(success, bpToDeleteId){
+    this.deleteBpCount++
+    if(success){
+      this.bpsToDelete.push(bpToDeleteId)
+    }
+    if(this.deleteBpCount === this.selectionLength){
+      for(let item of this.bpsToDelete){
+        
+        let indexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(item);
+        this.bpElements.splice(indexToDelete, 1)
+      }
+
+      if(this.resetFilter){
+        this.resetFilters()
+      }
+
+      this.selectedBPs = [];
+      this.bpsToDelete = [];
+      this.deleteBpCount = 0;
+      this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifacts Deleted' });
+    }
+  }
+
+  resetFilters(){
+    let filters: {field: string, value: string}[] = [];
+      filters.push({field: 'instanceName', value: this.filteredName})
+      filters.push({ field: 'instanceRelease', value: this.filteredRelease })
+      filters.push({ field: 'tag', value: this.filteredTag })
+      filters.push({ field: 'type', value: this.filteredType })
+      filters.push({ field: 'version', value: this.filteredVersion })
+      filters.push({ field: 'status', value: this.filteredStatus })
+    
+    for(let item of filters){
+      this.dt.filter(item.value, item.field, 'contains')
+    }
+  }
+
+  /* * * * Gets all blueprints * * * */
+  getAllBPs() {
+    this.spinnerService.show();
+    this.bpElements = [];
+    this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field }));
+
+    this.visible = "hidden";
+
+    this.bpApis.getAllBlueprints()
+      .subscribe((data: any[]) => {
+        this.fillTable(data)
+      })
+
+  }
+  
+  /* * * *  Checks when table is filtered and stores filtered data in new object to be downloaded when download button is clicked * * * */
+  onTableFiltered(values) {
+    if (values) {
+      this.filteredRows = values;
+    } else {
+      this.filteredRows = this.bpElements
+    }
+  }
+
+  /* * * * Download table as excel file * * * */
+  exportTable(exportTo) {
+    let downloadElements: any[] = []
+
+    for (let row of this.filteredRows) {
+      let labels;
+      let notes;
+      if (exportTo === "excel") {
+        if (row.metadata.labels !== undefined && row.metadata.labels !== null ) {
+          labels = row.metadata.labels.join(",")
+        }
+      } else {
+        labels = row.metadata.labels
+      }
+
+      if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') {
+        notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n")
+      }
+    
+      downloadElements.push({ 
+        Instance_Name: row.instanceName, 
+        Instance_Release: row.instanceRelease, 
+        Tag: row.tag, 
+        Type: row.type, 
+        Version: row.version, 
+        Status: row.status, 
+        Created_By: row.metadata.createdBy,
+        Created_On: row.metadata.createdOn,
+        Updated_By: row.metadata.updatedBy,
+        Updated_On: row.metadata.updatedOn,
+        Failure_Reason: row.metadata.failureReason,
+        Notes: notes,
+        Labels: labels
+      })
+    }
+    
+    let csvHeaders = []
+
+    if (exportTo === "csv") {
+      csvHeaders = [
+        "Instance_Name",
+        "Instance_Release",
+        "Tag",
+        "Type",
+        "Version",
+        "Status",
+        "Created_By",
+        "Created_On",
+        "Updated_By",
+        "Updated_On",
+        "Failure_Reason",
+        "Notes",
+        "Labels"];
+
+    }
+    
+    this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders)
+  }
+
+  /* * * * Fills object with blueprint data to be used to fill table * * * */
+  fillTable(data) {
+    let fileName: string;
+    let tag: string;
+    let type: string;
+
+    for (let elem of data) {
+      fileName = elem.fileName;
+      if(fileName.includes('docker')){
+        type = 'docker'
+        if(fileName.includes('-docker')){
+          tag = fileName.split('-docker')[0]
+        } else if (fileName.includes('_docker')){
+          tag = fileName.split('_docker')[0]
+        }
+      } else if (fileName.includes('k8s')){
+        type = 'k8s'
+        if (fileName.includes('-k8s')) {
+          tag = fileName.split('-k8s')[0]
+        } else if (fileName.includes('_k8s')) {
+          tag = fileName.split('_k8s')[0]
+        }
+      }
+      
+      //create temporary bp element to push to array of blueprints
+      var tempBpElement: BlueprintElement = {
+        instanceId:      elem.msInstanceInfo.id,
+        instanceName:    elem.msInstanceInfo.name,
+        instanceRelease: elem.msInstanceInfo.release,
+        id:              elem.id,
+        version:         elem.version,
+        content:         elem.content,
+        status:          elem.status,
+        fileName:        fileName,
+        tag:             tag, 
+        type:            type,
+        metadata: {
+          failureReason: elem.metadata.failureReason,
+          notes:         elem.metadata.notes,
+          labels:        elem.metadata.labels,
+          createdBy:     elem.metadata.createdBy,
+          createdOn:     this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'),
+          updatedBy:     elem.metadata.updatedBy,
+          updatedOn:     this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm')
+        },
+        specification: {
+          id:            elem.specificationInfo.id
+        }
+      }
+
+      this.bpElements.push(tempBpElement)
+    }
+    this.bpElements.reverse();
+    this.filteredRows = this.bpElements;
+
+    this.resetFilters();
+
+    this.visible = "visible";
+    this.spinnerService.hide();
+  }
+
+  /* * * * Define content to show in bp view dialog pop up * * * */
+  BpContentToView: string;
+  viewBpContent(data){
+    this.BpFileNameForDownload = `${data['tag']}_${data['type']}_${data['instanceRelease']}_${data['version']}`
+    this.BpContentToView = data['content']
+    this.showBpContentDialog = true
+  }
+
+  /* * * * Download single blueprint * * * */
+  BpFileNameForDownload: string;
+  download() {
+    let file = new Blob([this.BpContentToView], { type: 'text;charset=utf-8' });
+    let name: string = this.BpFileNameForDownload + '.yaml'
+    saveAs(file, name)
+  }
+
+/* * * * Download selected blueprints * * * */
+  downloadSelectedBps() {
+    let canDownloadBps: boolean = true;
+    
+    //checks if blueprints for multiple releases are selected
+    let selectedBpRelease: string = this.selectedBPs[0]['instanceRelease'];
+    for (let bp in this.selectedBPs) {
+      if (this.selectedBPs[bp]['instanceRelease'] !== selectedBpRelease) {
+        canDownloadBps = false
+        break
+      }
+    }
+
+    //downloads blueprints to zip file if all selected blueprints are for one release
+    if (canDownloadBps) {
+      var zip = new JSZip();
+      for (var i in this.selectedBPs) {
+        zip.file(`${this.selectedBPs[i]['tag']}_${this.selectedBPs[i]['type']}_${this.selectedBPs[i]['instanceRelease']}_${this.selectedBPs[i]['version']}.yaml`, this.selectedBPs[i]['content'])
+      }
+      zip.generateAsync({ type: "blob" }).then(function (content) {
+        saveAs(content, 'Blueprints.zip');
+      });
+    } else {
+      this.messageService.add({ key: 'multipleBpReleasesSelected', severity: 'error', summary: 'Error Message', detail: "Cannot download blueprints for different releases" });
+    }    
+
+    this.selectedBPs = []
+  }
+}
+
+export interface BlueprintElement{
+  instanceId: string
+  instanceName: string
+  instanceRelease: string
+  id: string
+  version: string
+  content: string
+  status: string
+  fileName: string
+  tag: string
+  type: string
+  metadata: {
+    failureReason: string
+    notes: string
+    labels: string[]
+    createdBy: string
+    createdOn: string
+    updatedBy: string
+    updatedOn: string
+  },
+  specification: {
+    id: string
+  }
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css
new file mode 100644 (file)
index 0000000..54806ab
--- /dev/null
@@ -0,0 +1,43 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.input {
+    padding-top: 10px;
+}
+
+.inputLabel {
+    font-weight: 600;
+    margin-left: 20px;
+    width: 135px;
+}
+
+.inputFieldSm {
+    width: 200px;
+    height: 35px;
+    padding-left: 6px;
+}
+.inputFieldMed {
+    width: 300px;
+    height: 35px;
+    padding-left: 6px;
+}
+.inputFieldLg {
+    width: 400px;
+    height: 35px;
+    padding-left: 6px;
+}
diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html
new file mode 100644 (file)
index 0000000..f15f19b
--- /dev/null
@@ -0,0 +1,64 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<p-dialog *ngIf="visible" header="Component Spec ADD" [(visible)]="visible" appendTo="body" [modal]="true" [transitionOptions]="'300ms'"
+          [closeOnEscape]="false" [closable]="false" [style]="{width: '650px'}" (onHide)="closeDialog()">
+
+    <!-- * * * * * Input fields * * * * * -->
+    <form [formGroup]="csAddForm" (ngSubmit)="saveCs()" class="bg-faded">
+        <!-- * * * Type * * * -->
+        <div class="input">
+            <label class="inputLabel">Type<span style="color:red">*</span></label>
+            <p-dropdown [options]="types" placeholder="Select Type" optionLabel="type" formControlName="type"></p-dropdown>
+        </div>
+        <!-- * * * Labels * * * -->
+        <div class="input">
+            <label class="inputLabel">Labels</label>
+            <input class="inputFieldLg" type="text" pInputText formControlName="labels" />
+        </div>
+        <span style="padding: 9px 0px 0px 158px; font-size: 13px;">(Separate labels with a space)</span>
+        <!-- * * * Notes * * * -->
+        <div class="input">
+            <label class="inputLabel" style="vertical-align: top">Notes</label>
+            <textarea class="inputFieldLg" [rows]="1" [cols]="30" pInputTextarea autoResize="autoResize" formControlName="notes"></textarea>
+        </div>
+        <!-- * * * Comp Spec File Select * * * -->
+        <div class="input">
+            <label class="inputLabel">Component Spec<span style="color:red">*</span></label>
+        
+            <input type="file" style="width: 460px; color:blue; font-style: italic;" (input)="onCompSpecUpload($event)" name="myfile" id="myfile" accept=".json">
+        </div>
+
+        <!-- * * * Policy File Select * * * -->
+        <div class="input">
+            <label class="inputLabel">Policy</label>
+        
+            <input type="file" style="width: 460px; color:blue; font-style: italic;" (input)="onPolicyUpload($event)"
+                name="myPolicyFile" id="myPolicyFile" accept=".json">
+        </div>
+
+        <!-- * * * ADD and Cancel buttons * * * -->
+        <div style="float: right; padding: 20px 45px">
+            <button pButton type="button" (click)="closeDialog()" label="Cancel"></button>&nbsp;
+            <button pButton type="submit" class="ui-button-success" label="Add" [disabled]="!csAddForm.valid || !compSpecSelected" style="width: 70px"></button>
+        </div>
+    </form>
+
+    <p-toast key="jsonError" [style]="{width: '430px'}"></p-toast>
+
+</p-dialog>
diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts
new file mode 100644 (file)
index 0000000..e29a110
--- /dev/null
@@ -0,0 +1,77 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ToastModule } from 'primeng/toast';
+
+
+import { CompSpecAddComponent } from './comp-spec-add.component';
+
+describe('CompSpecAddComponent', () => {
+  let component: CompSpecAddComponent;
+  let fixture: ComponentFixture<CompSpecAddComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [CompSpecAddComponent],
+      imports: [
+        DialogModule,
+        DropdownModule,
+        ToastModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CompSpecAddComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it(`should invalidate spec JSON structure`, async(() => {
+    const fixture = TestBed.createComponent(CompSpecAddComponent);
+    const app = fixture.debugElement.componentInstance;
+    let mockErrorJson = "test: 'test}"
+    app.compSpecContent = mockErrorJson
+    expect(() => app.validateJsonStructure()).toThrowError('JSON Structure error, quit!')
+  }));
+
+});
diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts
new file mode 100644 (file)
index 0000000..7564852
--- /dev/null
@@ -0,0 +1,180 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+import { InputTextModule } from 'primeng/inputtext';
+import { DropdownModule } from 'primeng/dropdown';
+import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';
+import { AuthService } from '../services/auth.service';
+import { MessageService } from 'primeng/api';
+
+interface Type {
+    type: string;
+}
+
+@Component({
+    selector: 'app-comp-spec-add',
+    templateUrl: './comp-spec-add.component.html',
+    styleUrls: ['./comp-spec-add.component.css']
+})
+
+export class CompSpecAddComponent implements OnInit {
+
+    compSpecSelected: any;
+    compSpecContent: any;
+
+    policySelected: any;
+    policyContent: any;
+
+    csAddForm: FormGroup;
+    //  The loggged in user
+    username: string;
+
+    //  Input form fields
+    type: string;
+    labels: string;
+    notes: string;
+    specFile: any;
+    policyJson: any;
+
+    //  Dropdowns
+    types: Type[];
+    
+    //  Build JSON as a string
+    csAddString: any;
+    //  Return JSON to parent component
+    csAddJson: any;
+    
+    constructor(private fb: FormBuilder, private authService: AuthService, private messageService: MessageService) {
+    }
+
+    @Input() visible: boolean;
+    @Output() handler: EventEmitter<any> = new EventEmitter();
+
+    ngOnInit() {
+        //  The logged in user
+        this.username = this.authService.getUser().username;
+
+        this.csAddForm = new FormGroup({
+            type: new FormControl(),
+            labels: new FormControl(),
+            notes: new FormControl(),
+            specFile: new FormControl(),
+            policyJson: new FormControl()
+        });
+    
+        //  FORM fields and validations
+        this.csAddForm = this.fb.group({
+            type: ['', [Validators.required]],
+            labels: ['', []],
+            notes: ['', []],
+            specFile: ['', []],
+            policyJson: ['', []]
+        });
+    
+        // TYPE Dropdown
+        this.types = [
+            { type: 'DOCKER' },
+            { type: 'K8S' }
+        ];
+    }
+
+    saveCs() {
+        this.createOutputJson();
+        this.csAddJson = JSON.stringify(this.csAddString);
+        this.handler.emit(this.csAddJson);
+        this.closeDialog();
+    }
+
+    //  Create the JSON to be sent to the parent component
+    //  The "labels" functions below take into account leading/trailing spaces, multiple spaces between labels, and conversion into an array
+    createOutputJson() {
+        this.validateJsonStructure();
+
+        let policy;
+        if(this.policyContent !== undefined){
+            policy = JSON.parse(this.policyContent)
+        } else {
+            policy = null
+        }
+
+        this.csAddString = {
+            specContent: JSON.parse(this.compSpecContent),
+            policyJson: policy,
+            type: this.csAddForm.value['type'].type,
+            metadata: {
+                labels: this.csAddForm.value['labels'].trim().replace(/\s{2,}/g, ' ').split(" "),
+                notes: this.csAddForm.value['notes']
+            },
+            user: this.username
+        };
+    }
+
+    //  Validate, catch, display JSON structure error, and quit!
+    validateJsonStructure() {
+        try {
+            JSON.parse(this.compSpecContent);
+        } catch (error) {
+            this.messageService.add({ key: 'jsonError', severity: 'error', summary: 'Invalid Component Spec JSON', detail: error, sticky: true });
+            throw new Error('JSON Structure error, quit!');
+        }
+        
+        if(this.policyContent !== undefined){
+            try {
+                JSON.parse(this.policyContent);
+            } catch (error) {
+                this.messageService.add({ key: 'jsonError', severity: 'error', summary: 'Invalid Policy JSON', detail: error, sticky: true });
+                throw new Error('JSON Structure error, quit!');
+            }
+        }
+    }
+
+    //  Read the selected Component Spec JSON file
+    onCompSpecUpload(event) {
+        this.compSpecSelected = event.target.files[0];
+        this.readCsFileContent(this.compSpecSelected);
+    }
+    //Read the selected Component Spec JSON file
+    onPolicyUpload(event) {
+        this.policySelected = event.target.files[0];
+        this.readPolicyFileContent(this.policySelected);
+    }
+
+    readCsFileContent(file) {
+        if (file) {
+            let fileReader = new FileReader();
+            fileReader.onload = (e) => { this.compSpecContent = fileReader.result; };
+            fileReader.readAsText(file);
+        }
+    }
+
+    readPolicyFileContent(file) {
+        if (file) {
+            let fileReader = new FileReader();
+            fileReader.onload = (e) => { this.policyContent = fileReader.result; };
+            fileReader.readAsText(file);
+        }
+    }
+
+    //  The handler emits 'null' back to parent to close dialog and make it available again when clicked
+    closeDialog() {
+        this.visible = false;
+        this.handler.emit(null);
+    }
+
+}
diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css
new file mode 100644 (file)
index 0000000..d76e0fa
--- /dev/null
@@ -0,0 +1,73 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.validator-input-card{
+    width: 40%;
+    min-width: 430px;
+    min-height: 100%;
+    border: 2px solid slategray;
+    border-radius: 5px;
+    background-color: white;
+    padding: 1%;
+}
+.validator-output-card{
+    width: 58%;
+    border: 2px solid slategray;
+    margin-left: 2%; 
+    min-height: 100%;
+    border-radius: 5px;
+    background-color: white;
+    padding: 1%;
+}
+
+.input-section-card{
+    font-size: 14px; 
+    border: solid slategray; 
+    border-style: double;
+    margin-top: 7px
+}
+
+.validateSpec{
+    margin-top: 10px;
+}
+.downloadSchema{
+    margin-top: 80px;
+}
+
+.type-selection{
+    position: absolute; 
+    background-color: rgba(128, 128, 128, 0.15); 
+    width: 190px; 
+    height: 40px; 
+    display: inline-flex; 
+    align-items: center; 
+    margin-left: 150px;
+}
+
+.greenOutput{
+    background-color: rgba(0, 255, 0, 0.205);
+    height: 40px;
+    padding: 3px;
+    border: 2px solid rgba(0, 66, 0, 0.747);
+}
+.redOutput{
+    background-color: rgba(255, 0, 0, 0.267);
+    height: 87%;
+    padding: 10px;
+    border: 2px solid rgba(65, 0, 0, 0.712);
+}
diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html
new file mode 100644 (file)
index 0000000..9867364
--- /dev/null
@@ -0,0 +1,108 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<div style="margin-left: 2%; margin-right: 2%;">
+    <p style="font-size: 26px;">Component Specification Validation</p>
+    <div style="display: inline-flex; width: 100%; height: 100%; min-height: 450px;">
+        <div class="validator-input-card">
+            <div>
+            <mat-card class="input-section-card">
+                <span><b>Action</b></span><span style="color:red">*</span><br>
+                <div style="display: inline-flex;">
+                    <div style="padding-right: 10%; width: 200px;">
+                        <p-radioButton name="spec-validator-actions" value="validateSpec" [(ngModel)]="spec_validator_action" (click)="validateRadioButton()"></p-radioButton>&nbsp;&nbsp;Validate Spec File
+                    </div>
+                    <div>
+                        <p-radioButton name="spec-validator-actions" value="downloadSchema" [(ngModel)]="spec_validator_action" (click)="downloadRadioButton()"></p-radioButton>&nbsp;&nbsp;Download Schema
+                    </div>
+                </div>
+            </mat-card>
+
+            <mat-card class="input-section-card">
+                <span><b>Release</b></span><span style="color:red">*</span><br>
+                <div>
+                    <div style="display: inline-flex; align-items: center;">
+                        <p-radioButton name="spec-validator-release" value="2007" [(ngModel)]="release"></p-radioButton>&nbsp;&nbsp;2007+
+                    </div>
+                </div>
+            </mat-card>
+
+            <mat-card class="input-section-card">
+                <span><b>Type</b></span><span style="color:red">*</span><br>
+                <div>
+                    <div style="display: inline-flex; align-items: center;">
+                        <p-radioButton name="spec-validator-type" value="k8s" [(ngModel)]="type"></p-radioButton>&nbsp;&nbsp;K8s
+                    </div>
+                    <br>
+                    <div style="display: inline-flex; align-items: center;">
+                        <p-radioButton name="spec-validator-type" value="docker" [(ngModel)]="type"></p-radioButton>&nbsp;&nbsp;Docker
+                    </div>
+                </div>
+            </mat-card>
+            
+            <mat-card *ngIf="spec_validator_action === 'validateSpec'" class="input-section-card">
+                <!-- * * * Comp Spec File Select * * * -->
+                <div>
+                    <b>Component Spec File</b><span style="color:red">*</span><br>
+                
+                    <div style="display: inline-flex;">
+                        <input #myFile type="file" style="color:blue; font-style: italic; width: fit-content"
+                            (input)="onCompSpecUpload($event)" name="myfile" accept=".json">
+                
+                        <button pButton type="button" (click)="resetFile()"
+                            style="background-color: transparent; border: none; height: 20px;"><i class="pi pi-times"
+                                style="color: black;"></i></button>
+                    </div>
+                </div>
+            </mat-card>
+
+            <div>
+                <div *ngIf="shouldValidate" style="background-color: rgba(128, 128, 128, 0.315); height: 2px; width: 100%; margin-top: 2%;"></div>
+                <div *ngIf="shouldDownload" style="background-color: rgba(128, 128, 128, 0.315); height: 2px; width: 100%; margin-top: 22%;"></div>
+
+                <div style="float: right; margin-top: 8px">
+                    <div *ngIf="shouldValidate" matTooltip="Fill In Required Fields" [matTooltipDisabled]="!(release === '' || type === '' || compSpecContent === null)" matTooltipPosition="above">
+                        <button [disabled]="release === '' || type === '' || compSpecContent === null" pButton label="Validate Spec" type="button" (click)="validateSpec()"></button>
+                    </div>
+                    <div *ngIf="shouldDownload" matTooltip="Fill In Required Fields" [matTooltipDisabled]="!(release === '' || type === '')" matTooltipPosition="above">
+                        <button [disabled]="release === '' || type === ''" pButton label="Download Schema" type="button" (click)="downloadSchema()"></button>
+                    </div>
+                </div>
+            </div>
+            </div>
+        </div>
+
+        <div *ngIf="shouldValidate" class="validator-output-card">
+            <span><b>Output:</b></span><br><br>
+            <div *ngIf="specValidated" style="width: 100%; padding: 1%; border-radius: 3px; overflow: hidden;" [ngClass]="{'greenOutput' : validCompSpec === true, 'redOutput' : validCompSpec === false}">
+                <div>
+                    <span style="font-weight: 500;">{{specValidationOutputHeader}}</span>
+                    <div *ngIf="specValidationOutputMessage !== ''" style="margin-top: 10px; width: 100%; height: 2px; background-color: rgba(128, 128, 128, 0.315); "></div>
+                    
+                    <div *ngIf="specValidationOutputMessage !== ''" style="margin-top: 10px">
+                        <p-scrollPanel [style]="{width: '100%', height: '50vh'}">
+                            <pre style="white-space: pre-wrap;"><b>Summary:</b><br>{{specValidationOutputSummary}}</pre>
+                            <pre style="white-space: pre-wrap;"><b>Message(s):</b><br>{{specValidationOutputMessage}}</pre>
+                        </p-scrollPanel>
+                    </div>
+                    
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts
new file mode 100644 (file)
index 0000000..a9efe28
--- /dev/null
@@ -0,0 +1,132 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { RadioButtonModule } from 'primeng/radiobutton';
+
+import { CompSpecValidationComponent } from './comp-spec-validation.component';
+import { FormsModule } from '@angular/forms';
+import { MatCardModule, MatTooltipModule } from '@angular/material';
+import { ScrollPanelModule } from 'primeng/scrollpanel';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { MessageService } from 'primeng/api';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+
+describe('CompSpecValidationComponent', () => {
+  let component: CompSpecValidationComponent;
+  let fixture: ComponentFixture<CompSpecValidationComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ CompSpecValidationComponent ],
+      imports: [ 
+        RadioButtonModule, 
+        FormsModule, 
+        MatCardModule,
+        MatTooltipModule,
+        ScrollPanelModule,
+        HttpClientTestingModule,
+        Ng4LoadingSpinnerModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CompSpecValidationComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it(`should have as shouldValidate 'false'`, () => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.shouldValidate).toEqual(false);
+  });
+
+  it(`should change shouldValidate to 'true'`, async(() => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+    app.validateRadioButton()
+    fixture.detectChanges();
+    expect(app.shouldValidate).toEqual(true);
+  }));
+
+  it(`should have as shouldDownload 'false'`, () => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.shouldDownload).toEqual(false);
+  });  
+
+  it(`should change shouldDownload to 'true'`, async(() => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+    app.downloadRadioButton()
+    fixture.detectChanges();
+    expect(app.shouldDownload).toEqual(true);
+  }));
+
+  it(`should set validation error message`, async(() => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+    let mockSuccess = {
+      status: 200
+    }
+    app.setSpecValidationMessage(mockSuccess)
+    fixture.detectChanges();
+    expect(app.validCompSpec).toEqual(true)
+  }));
+
+  it(`should set validation error message`, async(() => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockError = {
+      status: 400,
+      error: {
+        summary: 'Test',
+        errors: [
+          'error1',
+          'error2'
+        ]
+      }
+    }
+    app.setSpecValidationMessage(mockError)
+    fixture.detectChanges();
+    expect(app.validCompSpec).toEqual(false)
+  }));
+
+  it(`should invalidate JSON structure`, async(() => {
+    const fixture = TestBed.createComponent(CompSpecValidationComponent);
+    const app = fixture.debugElement.componentInstance;
+    let mockErrorJson = "test: 'test}"
+    app.compSpecContent = mockErrorJson
+    expect(() => app.validateJsonStructure()).toThrowError('JSON Structure error, quit!')  
+  }));
+
+});
diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts
new file mode 100644 (file)
index 0000000..2ce8fef
--- /dev/null
@@ -0,0 +1,145 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
+import { SpecValidationService } from '../services/spec-validation.service';
+import { DownloadService } from '../services/download.service';
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+
+@Component({
+  selector: 'app-comp-spec-validation',
+  templateUrl: './comp-spec-validation.component.html',
+  styleUrls: ['./comp-spec-validation.component.css']
+})
+export class CompSpecValidationComponent implements OnInit {
+
+  @ViewChild('myFile', {static: false})
+  myInputVariable: ElementRef;
+
+  spec_validator_action;
+  release = '';
+  type = '';
+  shouldValidate = false;
+  shouldDownload = false;
+  validCompSpec = false;
+  specValidated = false;
+  specValidationOutputSummary: any;
+  
+  constructor(private specValidator: SpecValidationService, private downloadService: DownloadService, private spinnerService: Ng4LoadingSpinnerService ) { }
+
+  ngOnInit() {
+
+  }
+
+  compSpecSelected: any;
+  onCompSpecUpload(event){
+    this.compSpecSelected = event.target.files[0];
+    this.readCsFileContent(this.compSpecSelected);
+  }
+
+  compSpecContent: any = null;
+  readCsFileContent(file) {
+    if (file) {
+      let fileReader = new FileReader();
+      fileReader.onload = (e) => { this.compSpecContent = fileReader.result; };
+      fileReader.readAsText(file);
+    }
+  }
+
+  validateRadioButton(){
+    this.shouldValidate = true;
+    this.shouldDownload = false;
+  }
+
+  downloadRadioButton(){
+    this.shouldValidate = false;
+    this.shouldDownload = true;
+    this.compSpecContent = null;
+    this.specValidated = false
+  }
+
+  resetFile(){
+    this.myInputVariable.nativeElement.value = "";
+    this.compSpecContent = null
+    this.compSpecSelected = null
+    this.specValidated = false
+  }
+
+  specValidationOutputHeader = ''
+  specValidationOutputMessage = '';
+  validateSpec(){
+    this.specValidationOutputHeader = ""
+    this.specValidationOutputMessage = ""
+    this.specValidationOutputSummary = ""
+
+    this.spinnerService.show()
+    this.validateJsonStructure()
+    this.specValidator.sendSpecFile(this.compSpecContent, this.type, this.release).subscribe(
+      res => {}, err => {
+        this.setSpecValidationMessage(err)
+      }
+    )
+  }
+
+  setSpecValidationMessage(res){
+    if(res.status === 200){
+      this.specValidationOutputHeader = "Success: Valid Component Spec"
+      this.specValidationOutputMessage = ""
+      this.validCompSpec = true
+    } else {
+      this.specValidationOutputHeader = `${res.status} Error: Invalid Component Spec`
+      this.specValidationOutputSummary = res.error.summary
+      
+      for(let item of res.error.errors){
+        this.specValidationOutputMessage += `- ${item}\n\n`
+      }
+
+      this.validCompSpec = false
+    }
+
+    this.specValidated = true;
+    this.spinnerService.hide()
+  }
+
+  validateJsonStructure() {
+    try {
+      JSON.parse(this.compSpecContent);
+    } catch (error) {
+      this.specValidationOutputHeader = "Error: Invalid Component Spec"
+      this.specValidationOutputSummary = "JSON Structure Error"
+      this.specValidationOutputMessage = error
+      this.validCompSpec = false
+      this.specValidated = true
+      this.spinnerService.hide()
+      throw new Error('JSON Structure error, quit!');
+    }
+  }
+
+  downloadSchema(){
+    this.spinnerService.show()
+    this.specValidator.getSchema(this.type).subscribe(
+      res => {
+        this.downloadService.downloadJSON(res, `${this.release}+_${this.type}_Schema`)
+        this.spinnerService.hide()
+      }, err => {
+        console.log(err)
+        this.spinnerService.hide()
+      }
+    )
+  }
+}
diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.css b/mod2/ui/src/app/comp-specs/comp-specs.component.css
new file mode 100644 (file)
index 0000000..5f86e73
--- /dev/null
@@ -0,0 +1,121 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+td{
+    word-break:break-all
+}
+
+textarea  
+{  
+   font-size: 12px;   
+}
+
+.table_actions_button{
+    background-color: transparent; 
+    border: none; 
+    width: 20px; 
+    height: 20px;  
+    vertical-align: middle;
+}
+
+.row-expand-layout{
+    display: grid; 
+    grid-template-columns: 30% 40% auto;
+    grid-gap: 10px; 
+    grid-auto-rows: minmax(100px, auto);
+}
+
+.row-expand-card{
+    font-size: 12px;
+    grid-row: 1;
+    border-radius: 5px;
+    border: 1px solid slategray;
+    padding: 10px;
+    /* This height prevents vertical scroll bar in Notes */
+    height: 92px;
+    overflow: hidden;
+}
+
+label {
+    cursor: pointer;
+}
+
+.fa-refresh{
+    cursor: pointer;
+}
+
+.input{
+    padding-top: 10px;
+}
+.inputLabel { 
+    font-weight: 600;
+    margin-left: 20px;
+    width: 140px;
+}
+
+.inputFieldSm { 
+    width: 200px; 
+    height: 35px; 
+    padding-left: 6px; 
+} 
+.inputFieldMed { 
+    width: 300px; 
+    height: 35px; 
+    padding-left: 6px; 
+} 
+.inputFieldLg { 
+    width: 400px; 
+    height: 35px; 
+    padding-left: 6px; 
+}
+
+.table_action_item{
+    outline: none;
+    font-size: 12px;
+}
+
+::ng-deep .mat-menu-content {
+padding-top: 0px !important;
+padding-bottom: 0px !important;
+}
+.mat-menu-item{
+line-height:30px;
+height:30px;
+}
+
+.greenStatus{
+    background-color: rgba(80, 233, 105, 0.87)
+}
+
+.redStatus{
+    background-color: rgba(255, 29, 29, 0.733)
+}
+
+.blueStatus{
+    background-color: rgba(0, 183, 255, 0.432)
+}
+
+.greyStatus{
+    background-color: rgba(150, 150, 150, 0.432)
+}
+
+.ui-state-highlight {
+    background-color: #878C94 !important;
+    color: black !important;
+}
diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.html b/mod2/ui/src/app/comp-specs/comp-specs.component.html
new file mode 100644 (file)
index 0000000..3061cdf
--- /dev/null
@@ -0,0 +1,189 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<div style="margin: 0px 50px 10px 20px; border: 1px solid darkslategray; min-width: 980px">
+    <p-table #dt *ngIf="loadTable" [columns]="cols" [value]="csElements" sortMode="multiple" [paginator]="true"
+        [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" (onFilter)="onTableFiltered(dt.filteredValue)" dataKey="id">
+        <ng-template pTemplate="caption">
+            
+            <div style="margin-left: -18%; width: 82%; max-height: 25px; display: inline-flex;">
+                <!--CS Table Header-->
+                <div style="float: left;">
+                    <!--Refresh-->
+                    <i class="fa fa-refresh" (click)="getAllCs()"></i>
+                    <!--Global Filter-->
+                    <input type="text" pInputText size="50" placeholder="Global Filter"
+                        (input)="dt.filterGlobal($event.target.value, 'contains')"
+                        style="width: 250px; height:25px; font-size: 12px; margin-left: 15px">
+                    <i class="fa fa-search" style="margin:4px 0px 0 8px"></i>
+                </div>
+            
+                <h4 style="margin-left: 15%"><b>Component Specs</b></h4>
+            
+            </div>
+        </ng-template>
+
+        <ng-template pTemplate="header" let-columns>
+            <tr>
+                <th style="width: 3em"></th>
+                <th class="ui-state-highlight" *ngFor="let col of columns" [pSortableColumn]="col.field"
+                    style="font-size: 12px; outline: none; vertical-align: bottom; text-align: center;" [ngStyle]="{'width': col.width}">
+                    {{col.header}}<br>
+                    <p-sortIcon [field]="col.field"></p-sortIcon>
+                </th>
+                <th style="font-size: 13px; width: 8%; vertical-align: top; text-align: center;">
+                    Actions
+                </th>     
+            </tr>
+
+            <!--Second header row for individual column filters-->
+            <tr style="text-align: center;">
+                <th style="width: 3em"></th>
+                <th *ngFor="let col of columns" [ngSwitch]="col.field">
+                    <input pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')"
+                        style="width: 100%; height: 20px; font-size: 10px;" placeholder="Filter">
+                </th>
+                <th>
+            
+                </th>
+            </tr>
+        </ng-template>
+
+        <ng-template pTemplate="body" let-rowData let-csElem>
+            <tr style="font-size: 12px;">
+                <!--Column for row expand buttons-->
+                <td>
+                    <a href="#" [pRowToggler]="rowData">
+                        <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+                    </a>
+                </td>
+            
+                <td *ngFor="let col of cols">
+                    <div *ngIf="col.field==='status'"
+                        style="width: fit-content; width: -moz-max-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;"
+                        [ngClass]="{'greenStatus' : csElem[col.field] === 'ACTIVE', 'greyStatus' : csElem[col.field] === 'INACTIVE'}">
+                        {{csElem[col.field]}}
+                    </div>
+                    <div *ngIf="col.field!=='status'">{{csElem[col.field]}}</div>
+                </td>
+            
+                <!--Actions Column-->
+                <td>
+                    <div style="text-align: center;">
+                        <button pButton type="button"
+                            style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;"
+                            class="ui-button-secondary" [matMenuTriggerFor]="menu">
+                            <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+                        </button>
+                        <mat-menu #menu="matMenu" xPosition="before">
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-search" style="font-size: 10px;"></i> View</span>
+                            </div>
+
+                            <button mat-menu-item class="table_action_item" (click)="showViewCsDialog(rowData)">Component Spec</button>
+                            <div matTooltip="Policy not included" [matTooltipDisabled]="rowData.policyJson" matTooltipPosition="left">
+                                <button mat-menu-item [disabled]="!rowData.policyJson" class="table_action_item" (click)="showViewPolicyDialog(rowData)">Policy</button>
+                            </div>
+                            <!--
+                            <div *ngIf="rowData.status !== 'ACTIVE'">
+                                <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                    <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"
+                                            style="font-size: 10px;"></i> Update</span>
+                                </div>
+                            
+                                <button mat-menu-item class="table_action_item" (click)="toActive(rowData)">To Active</button>
+                            </div>-->
+                        </mat-menu>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+
+        <!--Row expand content-->
+        <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+            <tr>
+                <td [attr.colspan]="columns.length + 2">
+                    <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+                        <!-- Audit Fields -->
+                        <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295)">
+                            <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+                            <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+                            <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+                            <b>Updated On:</b> {{rowData.metadata.updatedOn}}
+                        </div>
+                        <!-- Notes -->
+                        <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.219)">
+                            <b>Notes:</b><br>
+                            <p-scrollPanel [style]="{width: '100%', height: '62px'}">
+                                <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+                            </p-scrollPanel>
+                        </div>
+                        <!-- Labels -->
+                        <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.199)">
+                            <b style="padding-bottom: 5px;">Labels:</b><br>
+                            <div *ngFor="let label of rowData['metadata']['labels']"
+                                style="display: inline-flex; margin-top: 5px;">
+                                <div style="padding: 2px 7px 3px 0px;">
+                                    <span
+                                        style="background-color: rgba(80, 80, 80, 0.185); padding: 3px; border-radius: 3px;">{{label}}</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+    </p-table>
+
+    <!--download buttons for exporting table to either csv or excel file-->
+    <div *ngIf="loadTable" style="margin-left: 10px; margin-top: -32px; float: left;">
+        <button pButton type="button" (click)="exportTable('csv')" matTooltip="Export Table to CSV"
+            matTooltipPosition="above"
+            style="border-radius: 5px; width: 65px; height: 22px; font-size: 14px; border: none; margin-top: 4px; display: inline-flex;">
+            <i class="pi pi-file" style="margin-top: 2px; margin-left: 8px;"></i>
+            <label style="font-weight: 800; vertical-align: middle;">CSV</label>
+        </button>
+        <button pButton type="button" (click)="exportTable('excel')" matTooltip="Export Table to XLSX"
+            matTooltipPosition="above"
+            style="border-radius: 5px; width: 65px; height: 22px; margin-left: 7px; font-size: 14px; background-color: green; border: none; display: inline-flex;">
+            <i class="pi pi-file-excel" style="margin-top: 2px; margin-left: 4px;"></i>
+            <label style="font-weight: 800; vertical-align: middle;">Excel</label>
+        </button>
+    </div>
+</div>
+
+<!-- * * * * View Spec Content Pop Up * * * * -->
+<p-dialog [(visible)]="showViewCs" header="Spec Content" appendTo="body" [maximizable]="true"
+    [modal]="true" [style]="{width: '80vw'}" [baseZIndex]="10000" [closable]="false">
+    <pre>{{specContentToView | json}}</pre>
+    <p-footer>
+        <button pButton label="Close" (click)="showViewCs=false" type="button"></button>
+        <button pButton label="Download" (click)="download(specContentToView, 'Component_Spec')" type="button"></button>
+    </p-footer>
+</p-dialog>
+
+<!-- * * * * View Policy JSON Pop Up * * * * -->
+<p-dialog [(visible)]="showViewPolicy" header="Policy Json" appendTo="body" [maximizable]="true" [modal]="true"
+    [style]="{width: '80vw'}" [baseZIndex]="10000" [closable]="false">
+    <pre>{{policyJsonToView | json}}</pre>
+    <p-footer>
+        <button pButton label="Close" (click)="showViewPolicy=false" type="button"></button>
+        <button pButton label="Download" (click)="download(policyJsonToView, 'Policy_Json')" type="button"></button>
+    </p-footer>
+</p-dialog>
diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts b/mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts
new file mode 100644 (file)
index 0000000..64405e3
--- /dev/null
@@ -0,0 +1,133 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientModule } from '@angular/common/http';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatMenuModule, MatTooltipModule } from '@angular/material';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ScrollPanelModule } from 'primeng/scrollpanel';
+import { TableModule } from 'primeng/table';
+import { ToastModule } from 'primeng/toast';
+
+import { CompSpecsComponent } from './comp-specs.component';
+
+describe('CompSpecsComponent', () => {
+  let component: CompSpecsComponent;
+  let fixture: ComponentFixture<CompSpecsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        CompSpecsComponent
+      ],
+      imports: [
+        Ng4LoadingSpinnerModule,
+        TableModule,
+        MatMenuModule,
+        ScrollPanelModule,
+        ToastModule,
+        DialogModule,
+        DropdownModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        HttpClientModule,
+        ToastModule,
+        RouterTestingModule,
+        MatTooltipModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CompSpecsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it(`should fill csElements Object`, () => {
+    const fixture = TestBed.createComponent(CompSpecsComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockCsElement = [{
+      id: 'testId1234',
+      name: 'test-MS',
+      type: 'k8s',
+      specContent: '',
+      policyJson: 'test',
+      status: 'New',
+      msInstanceInfo: {
+        release: '2008',
+        name: 'test Ms',
+      },
+      metadata: {
+        createdBy: 'test',
+        createdOn: '01-01-2020 12:00',
+        updatedBy: 'test',
+        updatedOn: '01-01-2020 12:00',
+        notes: 'test',
+        labels: ['test'],
+      }
+    }]
+
+    app.fillTable(mockCsElement)
+
+    expect(app.loadTable).toEqual(true);
+    expect(app.csElements.length).toEqual(1);
+  });
+
+  it(`should set spec content to view`, () => {
+    const fixture = TestBed.createComponent(CompSpecsComponent);
+    const app = fixture.debugElement.componentInstance;
+    let mockData = {
+      specContent: 'test'
+    }
+    app.showViewCsDialog(mockData)
+    expect(app.showViewCs).toEqual(true);
+    expect(app.specContentToView).toEqual('test');    
+  });
+
+  it(`should set policy json content to view`, () => {
+    const fixture = TestBed.createComponent(CompSpecsComponent);
+    const app = fixture.debugElement.componentInstance;
+    let mockData = {
+      policyJson: 'test'
+    }
+    app.showViewPolicyDialog(mockData)
+    expect(app.showViewPolicy).toEqual(true);
+    expect(app.policyJsonToView).toEqual('test');
+  });  
+});
+
diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.ts b/mod2/ui/src/app/comp-specs/comp-specs.component.ts
new file mode 100644 (file)
index 0000000..4327c0b
--- /dev/null
@@ -0,0 +1,211 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
+import { compSpecsService } from '../services/comp-specs-service.service';
+import { Table } from 'primeng/table';
+import { MessageService } from 'primeng/api';
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+import { DatePipe } from '@angular/common';
+import { trigger, state, transition, style, animate } from '@angular/animations';
+import { ActivatedRoute } from '@angular/router';
+import { DownloadService } from '../services/download.service';
+
+@Component({
+  selector: 'app-comp-specs',
+  templateUrl: './comp-specs.component.html',
+  styleUrls: ['./comp-specs.component.css'],
+  animations: [
+    trigger('rowExpansionTrigger', [
+      state('void', style({
+        transform: 'translateX(-10%)',
+        opacity: 0
+      })),
+      state('active', style({
+        transform: 'translateX(0)',
+        opacity: 1
+      })),
+      transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+    ])
+  ],
+  providers: [DatePipe]
+})
+export class CompSpecsComponent implements OnInit {
+  @ViewChild(Table, { static: false }) dt: Table;
+
+  csElements: any[] = [];
+  cols: any[] = [
+    { field: 'instanceName', header: 'Instance Name' },
+    { field: 'release', header: 'Release', width: '15%' },
+    { field: 'type', header: 'Type', width: '15%' },
+    { field: 'policy', header: 'Policy', width: '15%' },
+    { field: 'status', header: 'Status', width: '15%' }
+  ];
+
+  columns: any[];
+  loadTable: boolean;
+  filteredRows: any;
+  summaryRows: any;
+  downloadItems: { label: string; command: () => void; }[];
+
+  msInstanceId: string;
+  msInstanceName: any;
+  msInstanceRelease: any;
+
+  constructor(private csApis: compSpecsService, private messageService: MessageService, 
+    private spinnerService: Ng4LoadingSpinnerService, private datePipe: DatePipe, 
+    private route: ActivatedRoute, private downloadService: DownloadService) { }
+
+  //create table of comp specs
+  ngOnInit() {
+    this.loadTable = false;
+
+    this.route.queryParams.subscribe((params) => {
+      this.msInstanceId = params['instanceId'];
+    });
+
+    this.getAllCs()
+  }
+
+  getAllCs() {
+    
+    this.csElements = [];
+
+    this.csApis.getAllCompSpecs(this.msInstanceId)
+      .subscribe((data: any[]) => {
+        this.fillTable(data)
+      })
+
+    this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field }));
+  }
+
+  //filter table
+  onTableFiltered(values) {
+    if (values) { this.filteredRows = values; }
+    else { this.filteredRows = this.summaryRows; }
+  }
+
+  /* * * * Export ms instance table to excel or csv * * * */
+  exportTable(exportTo) {
+    let downloadElements: any[] = []
+
+    //labels array not handled well by excel download so converted them to a single string
+    for (let row of this.filteredRows) {
+      let labels;
+      let notes;
+      if (exportTo === "excel") {
+        if (row.metadata.labels !== undefined) {
+          labels = row.metadata.labels.join(",")
+        }
+      } else {
+        labels = row.metadata.labels
+      }
+
+      if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') {
+        notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n")
+      }
+
+      downloadElements.push({
+        Instance_Name: row.instanceName,
+        Release: row.release,
+        Type: row.type,
+        Status: row.status,
+        Created_By: row.metadata.createdBy,
+        Created_On: row.metadata.createdOn,
+        Updated_By: row.metadata.updatedBy,
+        Updated_On: row.metadata.updatedOn,
+        Notes: notes,
+        Labels: labels
+      })
+    }
+
+    let arrHeader = []
+
+    if (exportTo === "csv") {
+      arrHeader = [
+        "Instance_Name",
+        "Release",
+        "Type",
+        "Status",
+        "Created_By",
+        "Created_On",
+        "Updated_By",
+        "Updated_On",
+        "Notes",
+        "Labels"
+      ];
+    }
+
+    this.downloadService.exportTableData(exportTo, downloadElements, arrHeader)
+  }
+
+  //fill object with microservice data, to be used to fill table. 
+  //checks if fields are empty and if they are, store 'N/A' as the values
+  fillTable(data) {
+    for (let elem of data) {
+      let policy = '';
+      if(elem.policyJson){policy = "Included"}
+
+      let tempCsElement: any = {
+        id: elem.id,
+        instanceName: elem.msInstanceInfo.name,
+        release: elem.msInstanceInfo.release,
+        type: elem.type,
+        policy: policy,
+        status: elem.status,
+        specContent: elem.specContent,
+        policyJson: elem.policyJson,
+        metadata: {
+          createdBy: elem.metadata.createdBy,
+          createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'),
+          updatedBy: elem.metadata.updatedBy,
+          updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm'),
+          notes: elem.metadata.notes,
+          labels: elem.metadata.labels
+        }
+      }
+      this.csElements.unshift(tempCsElement)
+    }
+    this.msInstanceName = this.csElements[0]['instanceName']
+    this.msInstanceRelease = this.csElements[0]['release']
+    this.filteredRows = this.csElements
+    this.loadTable = true;
+    this.spinnerService.hide();
+  }
+
+  showViewCs: boolean = false;
+  specContentToView: string;
+  showViewCsDialog(data) {
+    this.showViewCs = true;
+    this.specContentToView = data.specContent;
+  }
+  
+  showViewPolicy: boolean = false;
+  policyJsonToView: string;
+  showViewPolicyDialog(data) {
+    this.showViewPolicy = true;
+    this.policyJsonToView = data.policyJson;
+  }
+
+  /* * * * Download single spec file or policy * * * */
+  download(content, contentType) {
+    let fileName = `${this.msInstanceName}_${this.msInstanceRelease}_${contentType}`
+    this.downloadService.downloadJSON(content, fileName)
+  }
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/guards/auth.guard.spec.ts b/mod2/ui/src/app/guards/auth.guard.spec.ts
new file mode 100644 (file)
index 0000000..c82f8d6
--- /dev/null
@@ -0,0 +1,44 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { TestBed, async, inject } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { AuthGuard } from './auth.guard';
+
+describe('AuthGuard', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        AuthGuard,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ],
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule
+      ]
+    });
+  });
+
+  it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
+    expect(guard).toBeTruthy();
+  }));
+});
diff --git a/mod2/ui/src/app/guards/auth.guard.ts b/mod2/ui/src/app/guards/auth.guard.ts
new file mode 100644 (file)
index 0000000..cce544b
--- /dev/null
@@ -0,0 +1,62 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
+import { Observable, of } from 'rxjs';
+import { AuthService } from '../services/auth.service';
+import { catchError, map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AuthGuard implements CanActivate {
+
+  constructor(
+    private authService: AuthService,
+    private router: Router
+  ) {}
+
+  canActivate(
+    next: ActivatedRouteSnapshot,
+    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
+   
+      return this.authService.checkLogin().pipe(map((e:boolean)=>{
+        if(e){
+          this.authService.setUser();
+          if(this.authService.getUser().roles.includes("ROLE_ADMIN")){
+            this.authService.isAdmin = true;
+          }else{
+            this.authService.isAdmin = false;
+          }
+          this.authService.authPass=true;
+          return true;
+        } 
+      }), catchError(err=>{
+        this.authService.reLoginMsg = true;
+       // window.alert("Your login has expired. Please log in again.");
+        this.authService.authPass=false;
+        this.router.navigate(['/login']);
+        return of(false);
+      }))
+  }
+
+
+
+  
+}
diff --git a/mod2/ui/src/app/guards/login.guard.spec.ts b/mod2/ui/src/app/guards/login.guard.spec.ts
new file mode 100644 (file)
index 0000000..d58450c
--- /dev/null
@@ -0,0 +1,41 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { TestBed, async, inject } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { LoginGuard } from './login.guard';
+
+describe('LoginGuard', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        LoginGuard,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ],
+      imports: [HttpClientTestingModule, RouterTestingModule]
+    });
+  });
+
+  it('should ...', inject([LoginGuard], (guard: LoginGuard) => {
+    expect(guard).toBeTruthy();
+  }));
+});
diff --git a/mod2/ui/src/app/guards/login.guard.ts b/mod2/ui/src/app/guards/login.guard.ts
new file mode 100644 (file)
index 0000000..851ef61
--- /dev/null
@@ -0,0 +1,52 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
+import { Observable, of } from 'rxjs';
+import { AuthService } from '../services/auth.service';
+import { catchError, map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class LoginGuard implements CanActivate {
+  constructor(
+    private authService: AuthService,
+    private router: Router
+  ) {}
+
+  canActivate(
+    next: ActivatedRouteSnapshot,
+    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
+   
+      return this.authService.checkLogin().pipe(map((e:boolean)=>{
+        if(e){
+          console.log("Hi this is loginguard");
+          this.authService.authPass=true;
+          this.router.navigate(['/home']);
+          return true;
+        } 
+      }), catchError(err=>{
+        console.log("Login guard out");
+        this.authService.authPass=false;
+        this.router.navigate(['/login']);
+        return of(false);
+      }))
+}
+}
diff --git a/mod2/ui/src/app/guards/role.guard.spec.ts b/mod2/ui/src/app/guards/role.guard.spec.ts
new file mode 100644 (file)
index 0000000..c57dfe6
--- /dev/null
@@ -0,0 +1,44 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { TestBed, async, inject } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { RoleGuard } from './role.guard';
+
+describe('RoleGuard', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        RoleGuard,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ],
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule
+      ]
+    });
+  });
+
+  it('should ...', inject([RoleGuard], (guard: RoleGuard) => {
+    expect(guard).toBeTruthy();
+  }));
+});
diff --git a/mod2/ui/src/app/guards/role.guard.ts b/mod2/ui/src/app/guards/role.guard.ts
new file mode 100644 (file)
index 0000000..7e5b7ae
--- /dev/null
@@ -0,0 +1,61 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
+import { Observable, of } from 'rxjs';
+import { AuthService } from '../services/auth.service';
+import { catchError, map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RoleGuard implements CanActivate {
+
+
+  constructor(
+    private authService: AuthService,
+    private router: Router
+  ) {}
+
+  canActivate(
+    next: ActivatedRouteSnapshot,
+    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
+   
+      return this.authService.checkLogin().pipe(map((e:boolean)=>{
+        if(e){
+          console.log("role guard in");
+          this.authService.setUser();
+          if(this.authService.getUser().roles.includes("ROLE_ADMIN")){
+            this.authService.isAdmin = true;
+          }else{
+            this.authService.isAdmin = false;
+            this.router.navigate(['/home']);
+          }
+          return this.authService.getUser().roles.includes("ROLE_ADMIN");
+        } 
+      }), catchError(err=>{
+        this.authService.reLoginMsg = true;
+        // window.alert("Your login has expired. Please log in again.");
+        this.authService.authPass=false;
+        this.router.navigate(['/login']);
+        return of(false);
+      }))
+  
+}
+}
diff --git a/mod2/ui/src/app/home/home.component.css b/mod2/ui/src/app/home/home.component.css
new file mode 100644 (file)
index 0000000..f345db2
--- /dev/null
@@ -0,0 +1,50 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+
+.card{
+    display: inline-block;
+    margin-top: 56px;
+    margin-left: 75px;
+    color: rgb(34, 32, 32);
+    font-family: Arial, Helvetica, sans-serif;
+    font-weight: 800;
+    text-align: center;
+    padding-top: 42px;
+    font-size: 20px;
+    border-width: 2px;
+    border-color: gray;
+    border-radius: 8px;
+    height: 112px;
+    width: 261px;
+    cursor: pointer
+}
+
+#subMenu{
+  height: 75px;
+  width: 229px;
+  padding-top: 24px;
+  margin-left: 61px;
+  font-size: 18px;
+  border-radius: 5px
+}
+
+
+.mat-form-field + .mat-form-field {
+  margin-left: 8px;
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/home/home.component.html b/mod2/ui/src/app/home/home.component.html
new file mode 100644 (file)
index 0000000..31fe720
--- /dev/null
@@ -0,0 +1,93 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<div style="margin: -50px 0px 0px -30px">
+    <mat-card
+        (click)="toggleMsMenu()"
+        appMaterialElevation 
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16" 
+        class="card" 
+        style="background-color: #60a4a5">
+            Microservices...
+    </mat-card>
+    <mat-card 
+        (click)="navSelect('Onboarding Tools')"    
+        appMaterialElevation 
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16"
+        class="card"
+        style="background-color: rgba(154, 135, 167, 0.904);"
+            [routerLink]="'/OnboardingTools'">
+            Onboarding Tools
+    </mat-card>
+    <mat-card *ngIf="authService.isAdmin"
+        (click)="navSelect('User Management')"
+        appMaterialElevation 
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16"
+        class="card"
+        style="background-color: rgba(160, 120, 83, 0.904);"
+            [routerLink]="'/users'">
+            User Management
+    </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: -60px">
+    <mat-card id="subMenu" (click)="navSelect('Microservices')"
+        appMaterialElevation 
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16"
+        class="card"
+        style="background-color: #70a7a9; position: absolute;">
+            Microservices
+    </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: 70px">
+    <mat-card id="subMenu" (click)="navSelect('MS Instances')"
+        appMaterialElevation
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16"
+        class="card"
+        style="background-color: #8fbbbc; position: absolute;">
+            MS Instances
+    </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: 70px">
+    <mat-card id="subMenu" (click)="navSelect('Blueprints')"
+        appMaterialElevation 
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16"
+        class="card"
+        style="background-color: #afcecf; position: absolute;">
+            Blueprints
+    </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: 70px">
+    <mat-card id="subMenu" (click)="navSelect('MOD APIs')"
+        appMaterialElevation 
+        [defaultElevation]="defaultElevation" 
+        raisedElevation="16"
+        class="card"
+        style="background-color: #cfe2e2; position: absolute;">
+            MOD APIs
+    </mat-card>
+</div>
\ No newline at end of file
diff --git a/mod2/ui/src/app/home/home.component.ts b/mod2/ui/src/app/home/home.component.ts
new file mode 100644 (file)
index 0000000..36f9ee1
--- /dev/null
@@ -0,0 +1,64 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, HostBinding } from '@angular/core';
+import { AppComponent } from '../app.component';
+import { AuthService } from '../services/auth.service';
+import { BreadcrumbService } from '../services/breadcrumb.service';
+
+@Component({
+  selector: 'app-home',
+  templateUrl: './home.component.html',
+  styleUrls: ['./home.component.css']
+})
+export class HomeComponent implements OnInit {
+
+  defaultElevation = 2;
+  raisedElevation = 8;
+  panelOpenState = false;
+
+  name = 'Angular';
+
+  displayMsMenu = false;
+
+  constructor(private appComp: AppComponent, public authService: AuthService, private bread: BreadcrumbService) { }
+
+  ngOnInit() {
+  }
+
+  disableAnimation = true;
+  ngAfterViewInit(): void {
+    // timeout required to avoid the dreaded 'ExpressionChangedAfterItHasBeenCheckedError'
+    setTimeout(() => this.disableAnimation = false);
+  }
+
+    toggleMsMenu() {
+        if (this.displayMsMenu == false) {
+            this.displayMsMenu = true
+        } else {
+            this.displayMsMenu = false
+        }
+   }
+
+    navSelect(menuItem: any) {
+        this.appComp.tree_handler(menuItem, null);
+        // Set the breadcrumbs for the selected menu item (card)
+        this.bread.setBreadcrumbs(menuItem, "reset");
+    }
+        
+}
diff --git a/mod2/ui/src/app/login/login.component.css b/mod2/ui/src/app/login/login.component.css
new file mode 100644 (file)
index 0000000..55e48a1
--- /dev/null
@@ -0,0 +1,25 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.field-icon {
+    float:right;
+    left: -15px;
+    margin-top: 1px;
+    position: relative;
+    z-index: 2;
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/login/login.component.html b/mod2/ui/src/app/login/login.component.html
new file mode 100644 (file)
index 0000000..b98baff
--- /dev/null
@@ -0,0 +1,58 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<p-card header="Log in to MOD" [style]="{margin:'100px 300px 300px 300px'}">
+<hr class="line-break">
+<form [formGroup]="form" (ngSubmit)="submit()" class="p-5 bg-faded" style="margin-left: 220px;margin-bottom: 100px;">
+    <div class="ui-g ui-fluid" >
+        <div class="ui-g-12 ui-md-4">
+            <div class="ui-inputgroup" >
+                <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+                <input type="text" placeholder="ATT UID" formControlName="username">
+            </div>
+        </div>
+    </div>
+    <!-- <div class="ui-g ui-fluid" >
+        <div class="ui-g-12 ui-md-4">
+            <div class="ui-inputgroup" >
+                <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+                <input type="text" placeholder="ATT UID" formControlName="uid">
+            </div>
+        </div>
+    </div> -->
+    <div class="ui-g ui-fluid">
+        <div class="ui-g-12 ui-md-4">
+            <div class="ui-inputgroup" >
+                <span class="ui-inputgroup-addon" (click)="hide=!hide">
+                <i [ngClass]="hide? 'pi pi-eye-slash':'pi pi-eye'"></i></span>
+                <input [type]="hide? 'password':'text'" placeholder="password" formControlName="password" >  
+                <!-- <a routerLink="/reset-password">Forget password?</a>        -->
+            </div>
+        </div>
+    </div>
+    
+    <p-footer class="text-left ui-g-12">
+        <!-- <button pButton type="button" class="ui-button-info" label="Cancel" (click)="cancel()" style="margin-right: .25em"></button> -->
+        <button pButton type="submit" class="ui-button-success" label="Login" [disabled]="!form.valid"></button>
+    </p-footer>
+    <div class="text-left ui-g-12">
+        <p>Not a registered user? Contact the DCAE-MOD team at <i>dcae-mod-team@att.com</i></p> 
+        <a href="mailto:dcae-mod-team@att.com?subject=account application&body=Please help open a dcae account with username:">Cick here to contact now</a> 
+    </div>
+  </form>
+</p-card>
diff --git a/mod2/ui/src/app/login/login.component.spec.ts b/mod2/ui/src/app/login/login.component.spec.ts
new file mode 100644 (file)
index 0000000..5656be4
--- /dev/null
@@ -0,0 +1,59 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { CardModule } from 'primeng/card';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+  let component: LoginComponent;
+  let fixture: ComponentFixture<LoginComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [LoginComponent],
+      imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        CardModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LoginComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/login/login.component.ts b/mod2/ui/src/app/login/login.component.ts
new file mode 100644 (file)
index 0000000..16cafee
--- /dev/null
@@ -0,0 +1,65 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit } from '@angular/core';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { AuthService } from '../services/auth.service';
+import { User } from '../models/User';
+import { Router } from '@angular/router';
+
+@Component({
+    selector: 'app-login',
+    templateUrl: './login.component.html',
+    styleUrls: ['./login.component.css']
+})
+
+export class LoginComponent implements OnInit {
+
+  form: FormGroup;
+  hide: boolean=true;
+
+    constructor(private fb: FormBuilder, private authService: AuthService, private router: Router) { }
+
+    ngOnInit() {
+        this.form = this.fb.group({
+            username: ['', [Validators.required]],
+            // uid: ['', [Validators.required]],
+            password: ['', [Validators.required]]
+        });
+    }
+
+    submit() {
+        this.authService.login(this.form.value as User).subscribe(
+            res => {
+                // if (this.authService.getUser().roles && this.authService.getUser().roles.includes("ROLE_USER")) {
+                //     this.authService.isAdmin = false;
+                // }
+                if(this.authService.getUser().roles &&this.authService.getUser().roles.includes("ROLE_ADMIN")) {
+                    this.authService.isAdmin = true;
+                } else {
+                    this.authService.isAdmin = false;
+                }
+                this.router.navigate(['/home']);
+            }, 
+            (err) => {
+                alert('User or Password is not correct, please re-enter');
+                this.form.reset();
+            }
+        );
+    }
+}
diff --git a/mod2/ui/src/app/material-elevation.directive.ts b/mod2/ui/src/app/material-elevation.directive.ts
new file mode 100644 (file)
index 0000000..8843d1e
--- /dev/null
@@ -0,0 +1,64 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Directive, ElementRef, HostListener, Input, Renderer2, OnChanges, SimpleChanges } from '@angular/core';
+
+@Directive({
+    selector: '[appMaterialElevation]'
+})
+export class MaterialElevationDirective implements OnChanges {
+
+    @Input()
+    defaultElevation = 2;
+
+    @Input()
+    raisedElevation = 8;
+
+    constructor(
+        private element: ElementRef,
+        private renderer: Renderer2
+    ) {
+        this.setElevation(this.defaultElevation);
+    }
+
+    ngOnChanges(_changes: SimpleChanges) {
+        this.setElevation(this.defaultElevation);
+    }
+
+    @HostListener('mouseenter')
+    onMouseEnter() {
+        this.setElevation(this.raisedElevation);
+    }
+
+    @HostListener('mouseleave')
+    onMouseLeave() {
+        this.setElevation(this.defaultElevation);
+    }
+
+    setElevation(amount: number) {
+        // remove all elevation classes
+        const classesToRemove = Array.from((<HTMLElement>this.element.nativeElement).classList).filter(c => c.startsWith('mat-elevation-z'));
+        classesToRemove.forEach((c) => {
+            this.renderer.removeClass(this.element.nativeElement, c);
+        });
+
+        // add the given elevation class
+        const newClass = `mat-elevation-z${amount}`;
+        this.renderer.addClass(this.element.nativeElement, newClass);
+    }
+}
diff --git a/mod2/ui/src/app/microservices/microservices.component.css b/mod2/ui/src/app/microservices/microservices.component.css
new file mode 100644 (file)
index 0000000..5f86e73
--- /dev/null
@@ -0,0 +1,121 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+td{
+    word-break:break-all
+}
+
+textarea  
+{  
+   font-size: 12px;   
+}
+
+.table_actions_button{
+    background-color: transparent; 
+    border: none; 
+    width: 20px; 
+    height: 20px;  
+    vertical-align: middle;
+}
+
+.row-expand-layout{
+    display: grid; 
+    grid-template-columns: 30% 40% auto;
+    grid-gap: 10px; 
+    grid-auto-rows: minmax(100px, auto);
+}
+
+.row-expand-card{
+    font-size: 12px;
+    grid-row: 1;
+    border-radius: 5px;
+    border: 1px solid slategray;
+    padding: 10px;
+    /* This height prevents vertical scroll bar in Notes */
+    height: 92px;
+    overflow: hidden;
+}
+
+label {
+    cursor: pointer;
+}
+
+.fa-refresh{
+    cursor: pointer;
+}
+
+.input{
+    padding-top: 10px;
+}
+.inputLabel { 
+    font-weight: 600;
+    margin-left: 20px;
+    width: 140px;
+}
+
+.inputFieldSm { 
+    width: 200px; 
+    height: 35px; 
+    padding-left: 6px; 
+} 
+.inputFieldMed { 
+    width: 300px; 
+    height: 35px; 
+    padding-left: 6px; 
+} 
+.inputFieldLg { 
+    width: 400px; 
+    height: 35px; 
+    padding-left: 6px; 
+}
+
+.table_action_item{
+    outline: none;
+    font-size: 12px;
+}
+
+::ng-deep .mat-menu-content {
+padding-top: 0px !important;
+padding-bottom: 0px !important;
+}
+.mat-menu-item{
+line-height:30px;
+height:30px;
+}
+
+.greenStatus{
+    background-color: rgba(80, 233, 105, 0.87)
+}
+
+.redStatus{
+    background-color: rgba(255, 29, 29, 0.733)
+}
+
+.blueStatus{
+    background-color: rgba(0, 183, 255, 0.432)
+}
+
+.greyStatus{
+    background-color: rgba(150, 150, 150, 0.432)
+}
+
+.ui-state-highlight {
+    background-color: #878C94 !important;
+    color: black !important;
+}
diff --git a/mod2/ui/src/app/microservices/microservices.component.html b/mod2/ui/src/app/microservices/microservices.component.html
new file mode 100644 (file)
index 0000000..95ec45c
--- /dev/null
@@ -0,0 +1,185 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<ng4-loading-spinner [timeout]="1000000"></ng4-loading-spinner>
+<div style="margin: 0px 0px 10px 20px; width: 97%; min-width: 900px; border: 1px solid darkslategray">
+    <!--Table of base microservices-->
+    <p-table #dt *ngIf="loadTable" [columns]="cols" [value]="msElements" sortMode="multiple" [paginator]="true"
+        [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" (onFilter)="onTableFiltered(dt.filteredValue)" dataKey="id">
+
+        <!--Top caption row-->
+        <ng-template pTemplate="caption">
+
+            <div style="margin-left: -5%; width: 90%; max-height: 25px; display: inline-flex;">
+                <!--Microservices Table Header-->
+                <div style="float: left;">
+                    <!--Refresh-->
+                    <i class="fa fa-refresh" (click)="getAllMs()"></i>
+                    <!--Global Filter-->
+                    <input type="text" pInputText size="50" placeholder="Global Filter"
+                           (input)="dt.filterGlobal($event.target.value, 'contains')"
+                           style="width: 250px; height:25px; font-size: 12px; margin-left: 15px">
+                    <i class="fa fa-search" style="margin:4px 0px 0 8px"></i>
+                </div>
+
+                <h4 style="margin-left: 15%"><b>Microservices</b></h4>
+
+            </div>
+
+            <div style="float: right;">
+                <button pButton type="button" (click)="showAddChangeDialog()" matTooltip="Add Microservice" matTooltipPosition="above" 
+                    style="border-radius: 5px; width: 65px; height: 27px; font-size: 14px; border: none; display: inline-flex;">
+                    <i class="pi pi-plus" style="margin-top: 5px; margin-left: 10px;"></i>
+                    <label style="font-weight: 800; margin-top: 3px">MS</label>
+                </button>
+            </div>
+
+        </ng-template>
+
+        <!--Header row with dynamic column names. Columns include microservice Name, Type, Location and Namespace-->
+        <ng-template pTemplate="header" let-columns>
+            <tr style="text-align: center">
+                <th style="width: 3em"></th>
+                <th class="ui-state-highlight" *ngFor="let col of columns" [pSortableColumn]="col.field" style="font-size: 12px; outline: none; vertical-align: bottom;" [ngStyle]="{'width': col.width}">
+                    {{col.header}}<br>
+                    <p-sortIcon [field]="col.field"></p-sortIcon>
+                </th>
+                <th style="font-size: 13px; width: 6.5%; vertical-align: top;">
+                    Actions
+                </th>
+            </tr>
+
+            <!--Second header row for individual column filters-->
+            <tr style="text-align: center;">
+                <th style="width: 3em"></th>
+                <th *ngFor="let col of columns" [ngSwitch]="col.field">
+                    <input pInputText type="text"
+                        (input)="dt.filter($event.target.value, col.field, 'contains')" style="width: 100%; height: 20px; font-size: 10px;"
+                        placeholder="Filter">
+                </th>
+                <th></th>
+            </tr>
+        </ng-template>
+
+        <!--dynamic rows generated from columns object and msElems object-->
+        <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-msElem>
+            <tr style="font-size: 12px;">
+                <!--Column for row expand buttons-->
+                <td>
+                    <a href="#" [pRowToggler]="rowData">
+                        <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+                    </a>
+                </td>
+
+                <td *ngFor="let col of cols">
+                    <div *ngIf="col.field==='status'"
+                        style="width: fit-content; width: -moz-max-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;" 
+                        [ngClass]="{'greenStatus' : msElem[col.field] === 'ACTIVE',
+                                    'greyStatus' : msElem[col.field] === 'INACTIVE'}">
+                        {{msElem[col.field]}}
+                    </div>
+                    <div *ngIf="col.field!=='status'">{{msElem[col.field]}}</div>
+                </td>
+
+                <!--Actions Column-->
+                <td>
+                    <div style="text-align: center;">
+                        <button pButton type="button" style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;" class="ui-button-secondary" [matMenuTriggerFor]="menu">
+                            <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+                        </button>
+                        <mat-menu #menu="matMenu" xPosition="before">
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-plus"
+                                        style="font-size: 10px;"></i> Add</span>
+                            </div>
+                            
+                            <button mat-menu-item class="table_action_item" (click)="showAddChangeMsInstanceDialog(rowData)">Add MS
+                                Instance...</button>
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+                            </div>
+
+                            <button mat-menu-item class="table_action_item" (click)="showAddChangeDialog(rowData)">Update Microservice...</button>
+                        </mat-menu>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+
+        <!--Row expand content-->
+        <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+            <tr>
+                <td [attr.colspan]="columns.length + 2">
+                    <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+                        <!-- Audit Fields -->
+                        <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295)">                         
+                            <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+                            <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+                            <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+                            <b>Updated On:</b> {{rowData.metadata.updatedOn}}
+                        </div>
+                        <!-- Notes -->
+                        <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.219)">    
+                            <b>Notes:</b><br> 
+                            <p-scrollPanel [style]="{width: '100%', height: '62px'}">
+                                <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+                            </p-scrollPanel>
+                        </div>
+                        <!-- Labels -->
+                        <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.199)">
+                            <b style="padding-bottom: 5px;">Labels:</b><br>
+                            <div *ngFor="let label of rowData['metadata']['labels']" style="display: inline-flex; margin-top: 5px;">
+                                <div style="padding: 2px 7px 3px 0px;">
+                                    <span style="background-color: rgba(80, 80, 80, 0.185); padding: 3px; border-radius: 3px;">{{label}}</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+    </p-table>
+
+    <!--download buttons for exporting table to either csv or excel file-->
+    <div *ngIf="loadTable" style="margin-left: 10px; margin-top: -32px; float: left;">
+        <button pButton type="button" (click)="exportTable('csv')" 
+            matTooltip="Export Table to CSV" matTooltipPosition="above"
+            style="border-radius: 5px; width: 65px; height: 22px; font-size: 14px; border: none; margin-top: 4px; display: inline-flex;">
+            <i class="pi pi-file" style="margin-top: 2px; margin-left: 8px;"></i>
+            <label style="font-weight: 800; vertical-align: middle;">CSV</label>
+        </button>
+        <button pButton type="button" (click)="exportTable('excel')" 
+            matTooltip="Export Table to XLSX" matTooltipPosition="above" 
+            style="border-radius: 5px; width: 65px; height: 22px; margin-left: 7px; font-size: 14px; background-color: green; border: none; display: inline-flex;">
+            <i class="pi pi-file-excel" style="margin-top: 2px; margin-left: 4px;"></i>
+            <label style="font-weight: 800; vertical-align: middle;">Excel</label>
+        </button>
+    </div>
+
+    <!-- Dialog to Add or Change a MS -->
+    <app-ms-add-change *ngIf="showMsAddChangeDialog" [visible]="showMsAddChangeDialog" [currentRow]="currentRow" (handler)="addOrChangeMs($event)"></app-ms-add-change>
+    
+    <!-- Dialog to Add (or Change) a MS Instance -->
+    <app-ms-instance-add *ngIf="showAddChangeMsInstance" [visible]="showAddChangeMsInstance" [msName]="addInstanceTo" [currentRow]="currentRow" (handler)="addMsInstance($event)"></app-ms-instance-add>
+
+    <!-- Shared success message -->
+    <p-toast key="addChangeSuccess"></p-toast>
+
+</div>
\ No newline at end of file
diff --git a/mod2/ui/src/app/microservices/microservices.component.spec.ts b/mod2/ui/src/app/microservices/microservices.component.spec.ts
new file mode 100644 (file)
index 0000000..2d2e5f1
--- /dev/null
@@ -0,0 +1,134 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatMenuModule } from '@angular/material';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { CalendarModule } from 'primeng/calendar';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ScrollPanelModule } from 'primeng/scrollpanel';
+import { TableModule } from 'primeng/table';
+import { ToastModule } from 'primeng/toast';
+import { MsAddChangeComponent } from '../ms-add-change/ms-add-change.component';
+import { MsInstanceAddComponent } from '../ms-instance-add/ms-instance-add.component';
+
+import { MicroservicesComponent } from './microservices.component';
+
+describe('MicroservicesComponent', () => {
+  let component: MicroservicesComponent;
+  let fixture: ComponentFixture<MicroservicesComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        MicroservicesComponent,
+        MsAddChangeComponent,
+        MsInstanceAddComponent,
+      ],
+      imports: [
+        Ng4LoadingSpinnerModule,
+        TableModule,
+        MatMenuModule,
+        ScrollPanelModule,
+        ToastModule,
+        DialogModule,
+        DropdownModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        CalendarModule,
+        HttpClientTestingModule,
+        ToastModule,
+        RouterTestingModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MicroservicesComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it(`should fill msInstances Object`, () => {
+    const fixture = TestBed.createComponent(MicroservicesComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockMicroservice = [{
+      id: 'testId1234',
+      name: 'test-MS',
+      tag: 'test-MS-tag',
+      serviceName: 'testServiceName',
+      type: 'testType',
+      location: 'TestLocation',
+      namespace: 'testNameSpace',
+      status: 'testStatus',
+      metadata: {
+        createdBy: 'test',
+        createdOn: '01-01-2020 12:00',
+        updatedBy: 'test',
+        updatedOn: '01-01-2020 12:00',
+        notes: 'test',
+        labels: ['test'],
+      },
+      msInstances: 'test'
+    }]
+
+    app.fillTable(mockMicroservice)
+
+    expect(app.loadTable).toEqual(true);
+    expect(app.msElements.length).toEqual(1);
+  });
+
+  it(`should set addOrChange to "Add"`, () => {
+    const fixture = TestBed.createComponent(MicroservicesComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockRowData = null
+    app.showAddChangeDialog(mockRowData)
+
+    expect(app.addOrChange).toEqual('Add');
+  });
+
+  it(`should set addOrChange to "Change"`, () => {
+    const fixture = TestBed.createComponent(MicroservicesComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockRowData = {field: 'test'}
+    app.showAddChangeDialog(mockRowData)
+
+    expect(app.addOrChange).toEqual('Change');
+  });
+});
diff --git a/mod2/ui/src/app/microservices/microservices.component.ts b/mod2/ui/src/app/microservices/microservices.component.ts
new file mode 100644 (file)
index 0000000..640e750
--- /dev/null
@@ -0,0 +1,311 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, ViewChild, ElementRef, Input, EventEmitter, Output } from '@angular/core';
+import { MatTableDataSource } from '@angular/material/table';
+import { Table } from 'primeng/table';
+import { MessageService } from 'primeng/api';
+import { trigger, state, style, transition, animate } from '@angular/animations';
+import { BaseMicroserviceService } from '../services/base-microservice.service';
+import { MsAddService } from '../services/ms-add.service';
+import { MicroserviceInstanceService } from '../services/microservice-instance.service';
+import { AuthService } from '../services/auth.service';
+import { DatePipe } from '@angular/common';
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+import { DownloadService } from '../services/download.service';
+
+@Component({
+  selector: 'app-microservices',
+  templateUrl: './microservices.component.html',
+  styleUrls: ['./microservices.component.css'],
+  animations: [
+    trigger('rowExpansionTrigger', [
+      state('void', style({
+        transform: 'translateX(-10%)',
+        opacity: 0
+      })),
+      state('active', style({
+        transform: 'translateX(0)',
+        opacity: 1
+      })),
+      transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+    ])
+  ],
+  providers: [DatePipe]
+})
+export class MicroservicesComponent implements OnInit {
+    @ViewChild(Table, { static: false }) dt: Table;
+
+    /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+    msElements: any[] = [];
+    dataSource = new MatTableDataSource<any>(this.msElements);
+    cols: any[] = [
+        { field: 'name', header: 'MS Name' },
+        { field: 'tag', header: 'MS Tag' },
+        { field: 'serviceName', header: 'Service Short Name'},
+        { field: 'type', header: 'Type', width: '11%' },
+        { field: 'namespace', header: 'Namespace', width: '15%' },
+        { field: 'status', header: 'Status', width: '90px' }
+    ];
+    columns: any[];
+    loadTable: boolean;
+    filteredRows: any;
+    downloadItems: { label: string; command: () => void; }[];
+    showAddChangeMsInstance: boolean;
+    addInstanceTo: string = "";
+    msInstanceStates: { label: string, value: string }[] = [
+        { label: 'New',           value: 'new' },
+        { label: 'In Dev',        value: 'in-dev' },
+        { label: 'Dev Complete',  value: 'dev-complete' },
+        { label: 'In Test',       value: 'in-test' },
+        { label: 'Certified',     value: 'certified' },
+        { label: 'Prod Deployed', value: 'prod-deployed' }
+    ]
+
+    // Json to add MS to DB, returned from child
+    msAddChangeJson: any;
+
+    showMsAddChangeDialog: boolean = false;
+    currentRow: any;
+    currentMsRow: string = "";
+    addOrChange: string;
+    username: string;
+    errorMessage: any;
+    successMessage: string;
+
+    constructor(private spinnerService: Ng4LoadingSpinnerService, private baseMsService: BaseMicroserviceService,
+                private msInstanceApi: MicroserviceInstanceService, private messageService: MessageService,
+                private addChangeMsApi: MsAddService, private authService: AuthService, private datePipe: DatePipe,
+                private downloadService: DownloadService) { }
+
+    ngOnInit() {
+        this.username = this.authService.getUser().username;
+        this.getAllMs();
+    }
+
+    getAllMs() {
+        this.spinnerService.show();
+        this.msElements = [];
+        this.loadTable = false;
+        
+        this.baseMsService.getAllBaseMs()
+        .subscribe((data: any[]) => {
+            this.fillTable(data)
+        })
+
+        this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field }));
+    }
+
+    /*checks when table is filtered and stores filtered data in new 
+    object to be downloaded when download button is clicked*/
+    onTableFiltered(values) {
+        if (values !== null) {
+            this.filteredRows = values;
+        } else {
+            this.filteredRows = this.msElements;
+        }
+    }
+
+    //download table as excel file
+    exportTable(exportTo: string) {
+        let downloadElements: any[] = []
+
+        //labels array not handled well by excel download so converted them to a single string
+        for (let row of this.filteredRows) {
+            let labels;
+            let notes;
+            if (exportTo === "excel") {
+                if (row.metadata.labels !== undefined) {
+                    labels = row.metadata.labels.join(",")
+                }
+            } else {
+                labels = row.metadata.labels
+            }
+
+            if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') {
+                notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n")
+            }
+
+            downloadElements.push({
+                MS_Name: row.name,
+                MS_Tag: row.tag,
+                Service_Short_Name: row.serviceName,
+                Type: row.type,
+                Location: row.location,
+                Namespace: row.namespace,
+                Status: row.status,
+                Created_By: row.metadata.createdBy,
+                Created_On: row.metadata.createdOn,
+                Updated_By: row.metadata.updatedBy,
+                Updated_On: row.metadata.updatedOn,
+                Notes: notes,
+                Labels: labels
+            })
+        }
+        
+        let csvHeaders = []
+
+        if (exportTo === "csv") {
+            csvHeaders = [
+                "MS_Name", 
+                "MS_Tag", 
+                "Service_Short_Name", 
+                "Type", 
+                "Location", 
+                "Namespace", 
+                "Status", 
+                "Created_By", 
+                "Created_On", 
+                "Updated_By", 
+                "Updated_On", 
+                "Notes", 
+                "Labels" 
+            ];
+        }
+
+        this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders)
+    }
+
+    // * * * * * Show the Dialog to Add a MS (<app-ms-add-change> tag in the html) * * * * *
+    showAddChangeDialog(rowData) {
+        this.showMsAddChangeDialog = true;
+        this.currentRow = rowData;
+        if (this.currentRow) {
+            this.addOrChange = "Change";
+        } else {
+            this.addOrChange = "Add";
+        }
+    }
+
+    // * * * * * Add or Change a MS * * * * *
+    // The response includes the entire MS record that was Added or Changed, (along with ID and audit fields).
+    // When Added, the response is added directly to the table.  When Changed, the current record is updated field-by-field.
+    addOrChangeMs(jsonFromChildDialog) {
+        if (jsonFromChildDialog) {
+            this.msAddChangeJson = jsonFromChildDialog;
+            if (this.addOrChange == "Change") {
+                this.currentMsRow = this.currentRow['id']
+            }
+            this.addChangeMsApi.addChangeMsToCatalog(this.addOrChange, this.currentMsRow, this.msAddChangeJson).subscribe(
+                (response: any) => {
+                    if (this.addOrChange == "Add") {
+                        this.msElements.unshift(response);
+                        this.successMessage = "Microservice Added";
+                    } else {
+                        this.updateCurrentRow(jsonFromChildDialog);
+                        this.successMessage = "Microservice Updated";
+                    }
+                    this.showMsAddChangeDialog = false;
+                    this.messageService.add({ key: 'addChangeSuccess', severity: 'success', summary: 'Success', detail: this.successMessage, life: 5000 });
+                    },
+                errResponse => {
+                    // for testing only - this.updateCurrentRow(jsonFromChildDialog);
+                    this.messageService.add({ key: 'msAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.message, sticky: true });
+                }
+            )
+        }
+        else {
+            this.showMsAddChangeDialog = false;
+        };
+    }
+
+    updateCurrentRow(jsonFromChildDialog) {
+        const newRow = JSON.parse(jsonFromChildDialog);
+        this.currentRow['name']               = newRow['name'];
+        this.currentRow['serviceName']        = newRow['serviceName'];
+        this.currentRow['type']               = newRow['type'];
+        this.currentRow['location']           = newRow['location'];
+        this.currentRow['namespace']          = newRow['namespace'];
+        this.currentRow['metadata']['labels'] = newRow['metadata']['labels'];
+        this.currentRow['metadata']['notes']  = newRow['metadata']['notes'];
+    }
+
+    /* * * * Show pop up for Adding a new MS Instance * * * */
+    showAddChangeMsInstanceDialog(data) {
+        this.addInstanceTo = data['name']
+        this.showAddChangeMsInstance = true
+    }
+
+    /* * * * Call API to Add a new MS Instance * * * */
+    addMsInstance(body) {
+        if (body === null) {
+            this.showAddChangeMsInstance = false;
+        } else {
+            this.msInstanceApi.addChangeMsInstance("ADD", this.addInstanceTo, body).subscribe(
+                (data) => {
+                    this.messageService.add({ key: 'addChangeSuccess', severity: 'success', summary: 'Success', detail: "MS Instance Added", life: 5000 });
+                    this.showAddChangeMsInstance = false;
+                },
+                (errResponse) => {
+                    console.log(errResponse)
+                    this.messageService.add({ key: 'instanceAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.message, sticky: true });
+                }
+            )
+        }
+    }
+
+    //fill object with microservice data, to be used to fill table. 
+    //checks if fields are empty and if they are, store 'N/A' as the values
+    fillTable(data) {
+        for (let elem of data) {
+            var tempMsElement: any = {
+                id:          elem.id,
+                name:        elem.name,
+                tag:         elem.tag,
+                serviceName: elem.serviceName,
+                type:        elem.type,
+                location:    elem.location,
+                namespace:   elem.namespace,
+                status:      elem.status,
+                metadata: {
+                    createdBy: elem.metadata.createdBy,
+                    createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'),
+                    updatedBy: elem.metadata.updatedBy,
+                    updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm'),
+                    notes:     elem.metadata.notes,
+                    labels:    elem.metadata.labels
+                },
+                msInstances: elem.msInstances
+            }
+            this.msElements.push(tempMsElement)
+        }
+        this.filteredRows = this.msElements
+        this.loadTable = true;
+        this.spinnerService.hide();
+    }
+}
+
+export interface AddMsInstance{
+  name: string,
+  release: string, 
+  metadata: {
+    scrumLead: string,
+    scrumLeadId: string,
+    systemsEngineer: string,
+    systemsEngineerId: string,
+    developer: string;
+    developerId: string;
+    pstDueDate: string,
+    pstDueIteration: string,
+    eteDueDate: string,
+    eteDueIteration: string,
+    labels: string[],
+    notes: string
+  }
+  user: string
+}
diff --git a/mod2/ui/src/app/models/AuthResponse.ts b/mod2/ui/src/app/models/AuthResponse.ts
new file mode 100644 (file)
index 0000000..f44104d
--- /dev/null
@@ -0,0 +1,28 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Authority } from './Authority.enum';
+
+export class AuthResponse {
+    token?: string;
+    type?: null;
+    id?: string;
+    username?:string;
+    roles?: Authority[];
+    message?:string;
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/models/Authority.enum.ts b/mod2/ui/src/app/models/Authority.enum.ts
new file mode 100644 (file)
index 0000000..cbac582
--- /dev/null
@@ -0,0 +1,22 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+export enum Authority {
+    ADMIN = 'ROLE_ADMIN',
+    USER = 'ROLE_USER'
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/models/User.ts b/mod2/ui/src/app/models/User.ts
new file mode 100644 (file)
index 0000000..3c662d9
--- /dev/null
@@ -0,0 +1,25 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+export class User{
+    username: string;
+    fullName?: string;
+    password?: string;
+    active?: boolean;
+    roles?: any[];
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.css b/mod2/ui/src/app/ms-add-change/ms-add-change.component.css
new file mode 100644 (file)
index 0000000..f256b2f
--- /dev/null
@@ -0,0 +1,57 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.input {
+    padding-top: 10px;
+}
+
+.inputLabel {
+    font-weight: 600;
+    margin-left: 20px;
+    width: 150px;
+}
+
+.inputFieldSm {
+    width: 200px;
+    height: 35px;
+    padding-left: 6px;
+}
+.inputFieldMed {
+    width: 300px;
+    height: 35px;
+    padding-left: 6px;
+}
+.inputFieldLg {
+    width: 400px;
+    height: 35px;
+    padding-left: 6px;
+}
+
+.validationMsg {
+    padding-left: 175px;
+    color: red;
+    font-size: 11px;
+    font-weight: 550;
+}
+
+.validationMsgWarning {
+    padding-left: 175px;
+    color: rgb(255, 81, 0);
+    font-size: 11px;
+    font-weight: 550;
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.html b/mod2/ui/src/app/ms-add-change/ms-add-change.component.html
new file mode 100644 (file)
index 0000000..2fd19d7
--- /dev/null
@@ -0,0 +1,109 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<p-dialog *ngIf="visible" [header]="guiHeader" [(visible)]="visible" appendTo="body" [modal]="true" [transitionOptions]="'300ms'"
+          [closeOnEscape]="false" [closable]="false" [style]="{width: '645px'}" (onHide)="closeDialog()">
+
+    <!-- "Add / Change MS" Error Message -->
+    <p-toast key="msAddChangeError" [style]="{width: '500px'}"></p-toast>
+
+    <!-- * * * * * Input fields * * * * * -->
+    <form [formGroup]="msAddForm" (ngSubmit)="saveMs()" class="bg-faded">
+        <!-- * * * MS Name * * * -->
+        <div class="input">
+            <label class="inputLabel">MS Name<span style="color:red">*</span></label>
+            <input class="inputFieldMed" type="text" pInputText formControlName="name">
+        </div>
+        <!-- * * * MS Tag * * * -->
+        <div class="input">
+            <label class="inputLabel">MS Tag<span style="color:red">*</span></label>
+            <input *ngIf="!currentRow" class="inputFieldMed" type="text" pInputText formControlName="tag">
+            <!-- If Updating (vs ADDing) the data, the "Tag" is Read-Only -->
+            <input *ngIf="currentRow" class="inputFieldMed" type="text" pInputText formControlName="tag" style="border:none" readonly>
+            <!-- * * * (Validation Rules Display) * * * -->
+            <div class="validationMsg" *ngIf="msAddForm.controls['tag'].invalid &&
+                                             !msAddForm.value['tag']=='' &&
+                                              msAddForm.controls['tag'].value.length < 51">
+                Format: lowercase alphanumeric with embedded dashes (5-50 characters)
+            </div>
+            <!-- * * * (Validation Rule - length > 50) * * * -->
+            <div class="validationMsg" *ngIf="msAddForm.controls['tag'].value.length > 50">
+                MS Tag cannot exceed 50 chars
+            </div>
+        </div>
+        <!-- * * * Service Short Name * * * -->
+        <div class="input">
+            <label class="inputLabel">Service Short Name</label>
+            <input class="inputFieldMed" type="text" pInputText formControlName="serviceName">
+            <!-- * * * (Validation Rules Display) * * * -->
+            <div class="validationMsg" *ngIf="msAddForm.controls['serviceName'].invalid &&
+                                             !msAddForm.value['serviceName']=='' &&
+                                              msAddForm.controls['serviceName'].value.length < 26">
+                Format: lowercase alpha with embedded dashes
+            </div>
+            <!-- * * * (Warning! Global vs Central/Edge Service Name length) * * * -->
+            <div class="validationMsgWarning" *ngIf="msAddForm.controls['serviceName'].valid &&
+                                                     msAddForm.controls['serviceName'].value.length > 12 &&
+                                                     msAddForm.controls['serviceName'].value.length < 26">
+                Warning! Only Global Site short names can exceed 12 chars (max 25)
+            </div>
+            <!-- * * * (Validation Rule - length > 25) * * * -->
+            <div class="validationMsg" *ngIf="msAddForm.controls['serviceName'].value.length > 25">
+                Global Site short names cannot exceed 25 chars
+            </div>
+        </div>
+        <!-- * * * Type * * * -->
+        <div class="input">
+            <label class="inputLabel">Type<span style="color:red">*</span></label>
+            <p-dropdown [options]="types" placeholder="Select Type" optionLabel="type" formControlName="type"></p-dropdown>
+        </div>
+        <!-- * * * Location 
+        <div class="input">
+            <label class="inputLabel">Location<span style="color:red">*</span></label>
+            <p-dropdown [options]="locations" placeholder="Select Location" optionLabel="location" formControlName="location"></p-dropdown>
+        </div>
+        * * * -->
+        <!-- * * * Namespace * * * -->
+        <div class="input">
+            <label class="inputLabel">Namespace</label>
+            <input class="inputFieldMed" type="text" pInputText formControlName="namespace">
+            <!-- * * * (Validation Rules Display) * * * -->
+            <div class="validationMsg" *ngIf="msAddForm.controls['namespace'].invalid && !msAddForm.value['namespace']==''">
+                Format: lowercase alphanumeric with embedded dashes
+            </div>
+        </div>
+        <!-- * * * Labels * * * -->
+        <div class="input">
+            <label class="inputLabel">Labels</label>
+            <input class="inputFieldLg" type="text" pInputText formControlName="labels">
+        </div>
+        <span style="padding: 9px 0px 0px 172px; font-size: 13px;">(Separate labels with a space)</span>
+        <!-- * * * Notes * * * -->
+        <div class="input">
+            <label class="inputLabel" style="vertical-align: top">Notes</label>
+            <textarea class="inputFieldLg" [rows]="1" [cols]="30" pInputTextarea autoResize="autoResize" formControlName="notes"></textarea>
+        </div>
+        <!-- * * * ADD and CANCEL buttons * * * -->
+        <div style="float: right; padding: 20px 45px">
+            <button pButton type="button" (click)="closeDialog()" style="margin-right: 10px" label="Cancel"></button>
+            <button pButton type="submit" class="ui-button-success" style="width: 77px; text-align:center" [label]="addOrUpdate"
+                    [disabled]="!msAddForm.valid || !msAddForm.value['name'].trim()"></button>
+        </div>
+    </form>
+
+</p-dialog>
diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts b/mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts
new file mode 100644 (file)
index 0000000..4ba7ae9
--- /dev/null
@@ -0,0 +1,67 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ToastModule } from 'primeng/toast';
+
+import { MsAddChangeComponent } from './ms-add-change.component';
+
+describe('MsAddChangeComponent', () => {
+  let component: MsAddChangeComponent;
+  let fixture: ComponentFixture<MsAddChangeComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [MsAddChangeComponent],
+      imports: [
+        DialogModule,
+        DropdownModule,
+        ToastModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MsAddChangeComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.ts b/mod2/ui/src/app/ms-add-change/ms-add-change.component.ts
new file mode 100644 (file)
index 0000000..6991104
--- /dev/null
@@ -0,0 +1,180 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+import { InputTextModule } from 'primeng/inputtext';
+import { DropdownModule } from 'primeng/dropdown';
+import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';
+import { AuthService } from '../services/auth.service';
+
+interface Type {
+    type: string;
+}
+interface Location {
+    location: string;
+}
+
+@Component({
+    selector: 'app-ms-add-change',
+    templateUrl: './ms-add-change.component.html',
+    styleUrls: ['./ms-add-change.component.css']
+})
+
+export class MsAddChangeComponent implements OnInit {
+
+    guiHeader:    string = "Microservice ADD"
+    // Used for the Add/Update button label
+    addOrUpdate: string = "Add";
+
+    msAddForm: FormGroup;
+    // The loggged in user
+    username: string;
+
+    // Input form fields
+    name:        string;
+    tag:         string;
+    serviceName: string = "";
+    type:        string;
+    location:    string;
+    namespace:   string;
+    labels:      string;
+    notes:       string;
+
+    // Dropdowns
+    types:     Type[];
+    locations: Location[];
+
+    // Return JSON to parent component
+    msAddChangeString: any;
+    msAddChangeJson:   any;
+
+    constructor(private fb: FormBuilder, private authService: AuthService) {
+    }
+
+    @Input() visible: boolean;
+    @Input() currentRow: any;
+    @Output() handler: EventEmitter<any> = new EventEmitter();
+
+    ngOnInit() {
+        // The logged in user
+        this.username = this.authService.getUser().username;
+
+        this.msAddForm = new FormGroup({
+            name:        new FormControl(),
+            tag:         new FormControl(),
+            serviceName: new FormControl(),
+            type:        new FormControl(),
+            //location:    new FormControl(),
+            namespace:   new FormControl(),
+            labels:      new FormControl(),
+            notes:       new FormControl()
+        }); 
+
+        // FORM fields and validations
+        this.msAddForm = this.fb.group({
+            name:        ['', [Validators.required]],
+            tag:         ['', [Validators.required, Validators.pattern('^([a-z0-9](-[a-z0-9])*)+$'), Validators.minLength(5), Validators.maxLength(50)]],
+            serviceName: ['', [Validators.pattern('^([a-z](-[a-z])*)+$'), Validators.maxLength(25)]],
+            type:        ['', [Validators.required]],
+            //location:    ['', [Validators.required]],
+            namespace:   ['', [Validators.pattern('^([a-z0-9](-[a-z0-9])*)+$')]],
+            labels:      ['', []],
+            notes:       ['', []]
+            },
+            {updateOn: "blur"}
+        );
+
+        // TYPE Dropdown
+        this.types = [
+            { type: 'FM_COLLECTOR' },
+            { type: 'PM_COLLECTOR' },
+            { type: 'ANALYTIC' },
+            { type: 'TICK' },
+            { type: 'OTHER' }
+        ];
+        // LOCATION Dropdown
+        this.locations = [
+            { location: 'EDGE' },
+            { location: 'CENTRAL' },
+            { location: 'UNSPECIFIED' }
+        ];
+
+        // "Update" was selected, so populate the current row data in the GUI
+        if (this.currentRow) {
+            this.guiHeader = "Microservice Update";
+            this.addOrUpdate = "Update";
+            this.populateFields();
+        }
+
+    }
+
+    populateFields() {
+        let labelsStr: string;
+        if (this.currentRow['metadata']['labels']) {
+            labelsStr = this.currentRow['metadata']['labels'].join(' ')
+        }
+
+        // Prevent validation (length check) from failing in the html
+        if (this.currentRow['serviceName']) {
+             this.serviceName = this.currentRow['serviceName']
+        }
+
+        this.msAddForm.patchValue({
+            name:        this.currentRow['name'],
+            tag:         this.currentRow['tag'],
+            serviceName: this.serviceName,
+            type:        {type: this.currentRow['type']},
+            //location:    {location: this.currentRow['location']},
+            namespace:   this.currentRow['namespace'],
+            labels:      labelsStr,
+            notes:       this.currentRow['metadata']['notes']
+        })
+    }
+
+    // The handler emits 'null' back to parent to close dialog and make it available again when clicked
+    closeDialog() {
+        this.visible = false;
+        this.handler.emit(null);
+    }
+
+    // Create the JSON to be sent to the parent component
+    // The "labels" functions below take into account leading/trailing spaces, multiple spaces between labels, and conversion into an array
+    createOutputJson() {
+        this.msAddChangeString = {
+        name:        this.msAddForm.value['name'].trim(),
+        tag:         this.msAddForm.value['tag'],
+        serviceName: this.msAddForm.value['serviceName'],
+        type:        this.msAddForm.value['type'].type,
+        location:    'UNSPECIFIED',
+        //location:    this.msAddForm.value['location'].location,
+        namespace:   this.msAddForm.value['namespace'].trim(),
+        metadata: {
+            labels: this.msAddForm.value['labels'].trim().replace(/\s{2,}/g, ' ').split(" "),
+            notes:  this.msAddForm.value['notes']
+        },
+        user: this.username
+        };
+    }
+
+    saveMs() {
+        this.createOutputJson();
+        this.msAddChangeJson = JSON.stringify(this.msAddChangeString);
+        this.handler.emit(this.msAddChangeJson);
+    }
+
+}
diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css
new file mode 100644 (file)
index 0000000..8ecfc18
--- /dev/null
@@ -0,0 +1,48 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.input{
+    padding-top: 8px;
+}
+
+.inputLabel { 
+    font-weight: 600;
+    margin-left: 20px;
+    width: 165px;
+}
+
+.inputFieldXSm { 
+    width: 75px; 
+    height: 30px; 
+    padding-left: 6px; 
+} 
+.inputFieldSm { 
+    width: 200px; 
+    height: 30px; 
+    padding-left: 6px; 
+} 
+.inputFieldMed { 
+    width: 300px; 
+    height: 30px; 
+    padding-left: 6px; 
+} 
+.inputFieldLg { 
+    width: 400px; 
+    height: 30px; 
+    padding-left: 6px; 
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html
new file mode 100644 (file)
index 0000000..804de33
--- /dev/null
@@ -0,0 +1,93 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<p-dialog [(visible)]="visible" [header]="guiHeader" appendTo="body" [modal]="true" [transitionOptions]="'300ms'" [style]="{width: '720px'}" [baseZIndex]="10000"
+          [closable]="false" (onHide)="closeDialog()">
+
+    <!-- "Add / Change MS Instance" Error Message -->
+    <p-toast key="instanceAddChangeError"></p-toast>
+
+    <form [formGroup]="msInstanceAddForm">
+        <!-- * * * Name * * * -->
+        <div class="input">
+            <label class="inputLabel">MS Name</label>
+            <b>{{msName}}</b>
+        </div>
+        <!-- * * * Release * * * -->
+        <div class="input">
+            <label class="inputLabel">Release<span style="color:red">*</span></label>
+            <p-dropdown [options]="msInstanceReleases" placeholder="Select Release" formControlName="release"></p-dropdown>
+        </div>
+        <!-- * * * Scrum Lead / UID * * * -->
+        <div class="input">
+            <label class="inputLabel">Scrum Lead/UID</label>
+            <input class="inputFieldLg" type="text" pInputText formControlName="scrumLead" /> / <input class="inputFieldXSm" type="text" pInputText formControlName="scrumLeadId" />
+        </div>
+        <!-- * * * Systems Engineer / UID * * * -->
+        <div class="input">
+            <label class="inputLabel">Systems Engineer/UID</label>
+            <input class="inputFieldLg" type="text" pInputText formControlName="systemsEngineer" /> / <input class="inputFieldXSm" type="text" pInputText formControlName="systemsEngineerId" />
+        </div>
+        <!-- * * * Developer / UID * * * -->
+        <div class="input">
+            <label class="inputLabel">Developer<span style="color:red">*</span>/UID<span style="color:red">*</span></label>
+            <input class="inputFieldLg" type="text" pInputText formControlName="developer" /> / <input class="inputFieldXSm" type="text" pInputText formControlName="developerId" />
+        </div>
+        <!-- * * * PST Due Date * * * -->
+        <div class="input">
+            <label class="inputLabel">PST Due Date</label>
+            <p-calendar appendTo="body" [baseZIndex]="10001" dateFormat="yy-mm-dd" formControlName="pstDueDate" [showIcon]="true"></p-calendar>
+        </div>
+        <!-- * * * PST Due Iteration * * * -->
+        <div class="input">
+            <label class="inputLabel">PST Due Iteration</label>
+            <input class="inputFieldSm" type="text" pInputText formControlName="pstDueIteration" />
+        </div>
+        <!-- * * * ETE Due Date * * * -->
+        <div class="input">
+            <label class="inputLabel">ETE Due Date</label>
+            <p-calendar appendTo="body" [baseZIndex]="10001" dateFormat="yy-mm-dd" formControlName="eteDueDate" [showIcon]="true"></p-calendar>
+        </div>
+        <!-- * * * ETE Due Iteration * * * -->
+        <div class="input">
+            <label class="inputLabel">ETE Due Iteration</label>
+            <input class="inputFieldSm" type="text" pInputText formControlName="eteDueIteration" />
+        </div>
+        <!-- * * * Labels * * * -->
+        <div class="input">
+            <label class="inputLabel">Labels</label>
+            <input class="inputFieldLg" type="text" pInputText formControlName="labels" />
+        </div>
+        <span style="padding: 0px 0px 0px 188px; font-size: 13px;">(Separate labels with a space)</span>
+        <!-- * * * Notes * * * -->
+        <div class="input">
+            <label class="inputLabel" style="vertical-align: top">Notes</label>
+            <textarea class="inputFieldLg" [rows]="1" [cols]="30" pInputTextarea autoResize="autoResize" formControlName="notes"></textarea>
+        </div>
+
+        <!-- * * * ADD and Cancel buttons * * * -->
+        <div style="float: right; padding: 10px 25px 5px;">
+            <button pButton type="button" (click)="closeDialog()" label="Cancel"></button>&nbsp;
+            <button pButton type="submit" (click)="submitMsInstance()" class="ui-button-success" [label]="addOrUpdate" style="width: 77px"
+                [disabled]="!msInstanceAddForm.valid ||
+                            !msInstanceAddForm.value['developer'].trim() ||
+                            !msInstanceAddForm.value['developerId'].trim()"></button>
+        </div>
+    </form>
+
+</p-dialog>
\ No newline at end of file
diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts
new file mode 100644 (file)
index 0000000..2318a2c
--- /dev/null
@@ -0,0 +1,71 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { DatePipe } from '@angular/common';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { CalendarModule } from 'primeng/calendar';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ToastModule } from 'primeng/toast';
+
+import { MsInstanceAddComponent } from './ms-instance-add.component';
+
+describe('MsInstanceAddComponent', () => {
+  let component: MsInstanceAddComponent;
+  let fixture: ComponentFixture<MsInstanceAddComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [MsInstanceAddComponent],
+      imports: [
+        DialogModule,
+        DropdownModule,
+        ToastModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        HttpClientTestingModule,
+        RouterTestingModule,
+        CalendarModule,
+      ],
+      providers: [
+        MessageService,
+        DatePipe,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MsInstanceAddComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts
new file mode 100644 (file)
index 0000000..228ddc1
--- /dev/null
@@ -0,0 +1,189 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+import { MicroserviceInstanceService } from '../services/microservice-instance.service';
+import { MessageService } from 'primeng/api';
+import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
+import { AuthService } from '../services/auth.service';
+import { DatePipe } from '@angular/common';
+
+@Component({
+  selector: 'app-ms-instance-add',
+  templateUrl: './ms-instance-add.component.html',
+  styleUrls: ['./ms-instance-add.component.css']
+})
+export class MsInstanceAddComponent implements OnInit {
+
+  guiHeader: string = "Microservice Instance ADD";
+  // Used for the Add/Update button label
+  addOrUpdate: string = "Add";
+
+  msInstanceAddForm: FormGroup;
+  msInstanceToAdd: AddMsInstance;
+  addInstanceTo: string = "";
+  username: string;
+  msInstanceReleases: { label: string, value: string }[] = [
+    { label: '2004', value: '2004' },
+    { label: '2006', value: '2006' },
+    { label: '2008', value: '2008' },
+    { label: '2009', value: '2009' },
+    { label: '2010', value: '2010' },
+    { label: '2011', value: '2011' },
+    { label: '2012', value: '2012' }
+  ]
+
+  @Input() visible: boolean;
+  @Input() msName: string;
+  @Input() msInstanceChange: string;     // Use to differentiate Add from Change, since currentRow can be problematic
+  @Input() currentRow: any;
+  @Output() handler: EventEmitter<any> = new EventEmitter();
+
+  constructor(private addChangeMsInstanceApi: MicroserviceInstanceService, private messageService: MessageService, private fb: FormBuilder, private authService: AuthService, private datePipe: DatePipe) { }
+
+  ngOnInit() {
+    this.username = this.authService.getUser().username;
+
+    this.msInstanceAddForm = new FormGroup({
+      name: new FormControl(),
+      release: new FormControl(),
+      scrumLead: new FormControl(),
+      scrumLeadId: new FormControl(),
+      systemsEngineer: new FormControl(),
+      systemsEngineerId: new FormControl(),
+      developer: new FormControl(),
+      developerId: new FormControl(),
+      status: new FormControl(),
+      pstDueDate: new FormControl(),
+      pstDueIteration: new FormControl(),
+      eteDueDate: new FormControl(),
+      eteDueIteration: new FormControl(),
+      labels: new FormControl(),
+      notes: new FormControl()
+    });
+
+    this.msInstanceAddForm = this.fb.group({
+      name: ['', []],
+      release: ['', [Validators.required]],
+      scrumLead: ['', []],
+      scrumLeadId: ['', []],
+      systemsEngineer: ['', []],
+      systemsEngineerId: ['', []],
+      developer: ['', [Validators.required]],
+      developerId: ['', [Validators.required]],
+      status: ['', []],
+      pstDueDate: ['', []],
+      pstDueIteration: ['', []],
+      eteDueDate: ['', []],
+      eteDueIteration: ['', []],
+      labels: ['', []],
+      notes: ['', []]
+    });
+    
+    if (this.msInstanceChange) {
+        this.guiHeader   = "Microservice Instance Update";
+        this.addOrUpdate = "Update";
+        this.populateFields();
+    }
+  }
+
+  populateFields() {
+    this.msName = this.currentRow['name'];
+
+    let labelsStr: string;
+    if (this.currentRow['metadata']['labels']) {
+        labelsStr = this.currentRow['metadata']['labels'].join(' ')
+    }
+    
+    this.msInstanceAddForm.patchValue({
+        release:           this.currentRow['release'],
+        scrumLead:         this.currentRow['metadata']['scrumLead'],
+        scrumLeadId:       this.currentRow['metadata']['scrumLeadId'],
+        systemsEngineer:   this.currentRow['metadata']['systemsEngineer'],
+        systemsEngineerId: this.currentRow['metadata']['systemsEngineerId'],
+        developer:         this.currentRow['metadata']['developer'],
+        developerId:       this.currentRow['metadata']['developerId'],
+        pstDueDate:        this.currentRow['pstDueDate'],
+        pstDueIteration:   this.currentRow['pstDueIteration'],
+        eteDueDate:        this.currentRow['eteDueDate'],
+        eteDueIteration:   this.currentRow['eteDueIteration'],
+        labels:            labelsStr,
+        notes:             this.currentRow['metadata']['notes']
+    })
+}
+
+  /* * * * On click of cancel * * * */
+  closeDialog() {
+    this.visible = false;
+    this.handler.emit(null)
+  }
+
+  /* * * * On click of add * * * */
+  submitMsInstance() {
+    //  Prevent error on "split" if record existed before "labels" were implemented
+    let labels: string[] = []
+    if (!this.msInstanceAddForm.value['labels']){
+       labels = []
+    } else {
+        labels = this.msInstanceAddForm.value['labels'].trim().replace(/\s{2,}/g, ' ').split(" ")
+    }
+
+    //build request body
+    this.msInstanceToAdd = {
+      name:    this.msName,
+      release: this.msInstanceAddForm.value['release'],
+      metadata: {
+        scrumLead:         this.msInstanceAddForm.value['scrumLead'],
+        scrumLeadId:       this.msInstanceAddForm.value['scrumLeadId'],
+        systemsEngineer:   this.msInstanceAddForm.value['systemsEngineer'],
+        systemsEngineerId: this.msInstanceAddForm.value['systemsEngineerId'],
+        developer:         this.msInstanceAddForm.value['developer'],
+        developerId:       this.msInstanceAddForm.value['developerId'],
+        pstDueDate:        this.msInstanceAddForm.value['pstDueDate'],
+        pstDueIteration:   this.msInstanceAddForm.value['pstDueIteration'],
+        eteDueDate:        this.msInstanceAddForm.value['eteDueDate'],
+        eteDueIteration:   this.msInstanceAddForm.value['eteDueIteration'],
+        labels:            labels,
+        notes:             this.msInstanceAddForm.value['notes']
+      },
+      user: this.username
+    }
+
+    this.handler.emit(this.msInstanceToAdd) //return request body back to parent
+  }
+}
+
+export interface AddMsInstance {
+  name: string,
+  release: string,
+  metadata: {
+    scrumLead: string,
+    scrumLeadId: string,
+    systemsEngineer: string,
+    systemsEngineerId: string,
+    developer: string,
+    developerId: string,
+    pstDueDate: any,
+    pstDueIteration: string,
+    eteDueDate: any,
+    eteDueIteration: string,
+    labels: string[],
+    notes: string
+  }
+  user: string
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/msInstances/msInstances.component.css b/mod2/ui/src/app/msInstances/msInstances.component.css
new file mode 100644 (file)
index 0000000..62c5ac8
--- /dev/null
@@ -0,0 +1,148 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+td{
+    word-break:break-all;
+    font-size: 12px;
+}
+
+th{
+    text-align: center;
+    font-size: 12px;
+}
+
+.table_column_filter{
+    width: 100%; 
+    height: 20px; 
+    font-size: 10px;
+}
+
+.table_div{
+    margin: 0px 50px 10px 20px; 
+    min-width: 980px; 
+    width: 98%;
+    border: 1px solid darkslategray;
+}
+
+.fa-refresh{
+    cursor: pointer;
+}
+
+textarea  
+{  
+   font-size: 12px;   
+}
+
+.row-expand-layout{
+    display: grid; 
+    grid-template-columns: 17% 30% 30% auto;
+    grid-gap: 10px; 
+    grid-auto-rows: minmax(100px, auto);
+}
+
+.row-expand-card{
+    font-size: 12px;
+    border-radius: 5px;
+    border: 1px solid slategray;
+    padding: 10px;
+    /* This height prevents vertical scroll bar in Notes */
+    height: 92px;
+    overflow: hidden;
+    min-width: 195px;
+}
+
+.table_export_buttons_alignment{
+    margin-left: 5px; 
+    margin-top: -32px; 
+    float: left;
+}
+
+.table_export_button{
+    border-radius: 5px; 
+    height: 22px; 
+    font-size: 14px; 
+    border: none; 
+    margin-top: 4px; 
+    margin-right: 7px;
+    display: inline-flex;
+}
+
+.table_caption_header{
+    margin-left: -18%; 
+    width: 82%; 
+    max-height: 25px; 
+    display: inline-flex;
+}
+
+.table_global_filter{
+    width: 250px;
+    height: 25px;
+    margin-left: 15px;
+    font-size: 12px;
+}
+
+.table_title{
+    margin-left: 15%;
+}
+
+.table_actions_button{
+    background-color: transparent; 
+    border: none; 
+    width: 20px; 
+    height: 20px;  
+    vertical-align: middle;
+}
+
+.table_action_item{
+    outline: none;
+    font-size: 12px;
+}
+
+::ng-deep .mat-menu-content {
+    padding-top: 0px !important;
+    padding-bottom: 0px !important;
+}
+.mat-menu-item{
+    line-height:30px;
+    height:30px;
+}
+
+.greenStatus{
+    background-color: rgba(80, 233, 105, 0.87)
+}
+
+.redStatus{
+    background-color: rgba(255, 29, 29, 0.733)
+}
+
+.blueStatus{
+    background-color: rgba(0, 183, 255, 0.432)
+}
+
+.greyStatus{
+    background-color: rgba(150, 150, 150, 0.432)
+}
+
+.toast-newline {
+    white-space: pre-line;
+}
+
+.ui-state-highlight {
+    background-color: #878C94 !important;
+    color: black !important;
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/msInstances/msInstances.component.html b/mod2/ui/src/app/msInstances/msInstances.component.html
new file mode 100644 (file)
index 0000000..1545de3
--- /dev/null
@@ -0,0 +1,221 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<ng4-loading-spinner [timeout]="1000000"></ng4-loading-spinner>
+<div class="table_div">
+    <p-table #dt *ngIf="loadTable" [columns]="cols" [value]="msInstances" sortMode="multiple" [paginator]="true" 
+    [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" selectionMode="multiple" 
+    [(selection)]="selectedMsInstances" (onFilter)="onTableFiltered(dt.filteredValue)" dataKey="id">
+        <ng-template pTemplate="caption">
+        
+            <div class="table_caption_header">
+                <!--Microservices Table Header-->
+                <div style="float: left;">
+                    <!--Refresh-->
+                    <i class="fa fa-refresh" (click)="getAllInstances()"></i>
+                    <!--Global Filter-->
+                    <input type="text" pInputText size="50" placeholder="Global Filter"
+                           (input)="dt.filterGlobal($event.target.value, 'contains')"
+                           class="table_global_filter">
+                    <i class="fa fa-search" style="margin:4px 0 0 8px"></i>
+                </div>
+        
+                <div class="table_title">
+                    <h4><b>Microservice Instances</b></h4>
+                </div>
+            </div>
+        
+         </ng-template>
+
+        <!--column headers-->
+        <ng-template pTemplate="header" let-columns>
+            <tr>
+                <th style="width: 3em"></th>
+                <th class="ui-state-highlight" *ngFor="let col of columns" style="outline: none; vertical-align: bottom; text-align: center;" [pSortableColumn]="col.field" [ngStyle]="{'width': col.width}">
+                    {{col.header}}<br>
+                    <p-sortIcon [field]="col.field" style="font-size: 8px;"></p-sortIcon>
+                </th>
+                <!--actions column-->
+                <th style="width: 7%;">
+                    Actions
+                </th>
+            </tr>
+            <!--Second header row for individual column filters-->
+            <tr>
+                <th style="width: 3em"></th>
+                <th *ngFor="let col of columns" [ngSwitch]="col.field">
+                    <input pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+                </th>
+                <th>
+                    <div style="text-align: center;">
+                        <p-tableHeaderCheckbox style="padding-right: 5px;"></p-tableHeaderCheckbox>
+                        <button pButton type="button" class="ui-button-secondary" (click)="checkCanGenerateBp()" [matMenuTriggerFor]="menu"
+                            style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;">
+                            <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+                        </button>
+                        <mat-menu #menu="matMenu" xPosition="before">
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-plus"
+                                        style="font-size: 10px;"></i> Add</span>
+                            </div>
+
+                            <div matTooltip="{{generateSelectedBPsTooltip}}" [matTooltipDisabled]="canGenerateSelectedBPs" matTooltipPosition="left">
+                                <button mat-menu-item (click)="generateSelectedBlueprints()" class="table_action_item" [disabled]="!canGenerateSelectedBPs" style="margin-top: 5px;">Generate Selected Blueprints</button>
+                            </div>
+                        </mat-menu>
+                    </div>
+                </th>
+            </tr>
+        </ng-template>
+
+        <!--row data-->
+        <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-msElem>
+            <tr>
+                <!--Column for row expand buttons-->
+                <td>
+                    <a href="#" [pRowToggler]="rowData">
+                        <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+                    </a>
+                </td>
+
+                <td *ngFor="let col of cols">
+                    <div *ngIf="col.field==='status'"
+                        style="width: fit-content; width: -moz-max-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;" [ngClass]="{
+                                                'greenStatus' : msElem[col.field] === 'DEV_COMPLETE' || msElem[col.field] === 'CERTIFIED' || msElem[col.field] === 'PROD_DEPLOYED',
+                                                'blueStatus' : msElem[col.field] === 'NEW' || msElem[col.field] === 'IN_DEV' || msElem[col.field] === 'IN_TEST'}">
+                        {{msElem[col.field]}}
+                    </div>
+                    <div *ngIf="col.field!=='status'">{{msElem[col.field]}}</div>
+                </td>
+
+                <td>
+                    <div style="text-align: center">
+                        <p-tableCheckbox [value]="rowData" style="padding-right: 5px;"></p-tableCheckbox>
+                        <button pButton type="button" class="ui-button-secondary" [matMenuTriggerFor]="menu" 
+                            style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;">
+                            <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+                        </button>
+                        <mat-menu #menu="matMenu" xPosition="before">
+                            
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-plus" style="font-size: 10px;"></i> Add</span>
+                            </div>
+                            <!-- * * * * Add component spec * * * * -->
+                            <button mat-menu-item class="table_action_item" (click)="showAddCSDialog(rowData)">Component Spec...</button>
+                            <!-- * * * * Generate Blueprint * * * * -->
+                            <div matTooltip="No Active Component Spec" [matTooltipDisabled]="rowData.activeSpec!==null" matTooltipPosition="left">
+                                <button mat-menu-item class="table_action_item" (click)="generateBlueprints(rowData)" [disabled]="rowData.activeSpec===null">Generate Blueprint</button>
+                            </div>
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-search" style="font-size: 10px;"></i>  View</span>
+                            </div>
+                            <!-- * * * * Go to spec files table * * * * -->
+                            <div matTooltip="No Component Specs" [matTooltipDisabled]="rowData.activeSpec!==null" matTooltipPosition="left">
+                                <button mat-menu-item class="table_action_item" (click)="viewCompSpecs(rowData)" [disabled]="rowData.activeSpec===null">Component Specs</button>
+                            </div>
+                            <!-- * * * * Go to blueprints table * * * * -->
+                            <button mat-menu-item class="table_action_item" (click)="viewBlueprints(rowData)" [disabled]="rowData.activeSpec===null">Blueprints</button>
+
+                            <div style="background-color: rgba(128, 128, 128, 0.25);">
+                                <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+                            </div>
+                            <!-- * * * * Update ms instance record * * * * -->
+                            <button mat-menu-item class="table_action_item" (click)="showAddChangeDialog(rowData)">Update MS Instance...</button>
+                        </mat-menu>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+
+        <!--Row expand content-->
+        <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+            <tr>
+                <td [attr.colspan]="columns.length + 2">
+                    <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+                        <!-- Audit Fields -->
+                        <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295);">
+                            <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+                            <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+                            <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+                            <b>Updated On:</b> {{rowData.metadata.updatedOn}}
+                        </div>
+                        <!-- People -->
+                        <div class="row-expand-card" style="background-color: rgba(160, 159, 95, 0.295)">
+                            <b>Scrum Lead: </b>{{rowData.metadata.scrumLead}}
+                                <span *ngIf="rowData.metadata.scrumLeadId"> ({{rowData.metadata.scrumLeadId}})</span><br/>
+                            <b>Systems Engineer: </b>{{rowData.metadata.systemsEngineer}}
+                                <span *ngIf="rowData.metadata.systemsEngineerId"> ({{rowData.metadata.systemsEngineerId}})</span><br/>
+                            <b>Developer: </b>{{rowData.metadata.developer}}
+                                <span *ngIf="rowData.metadata.developerId"> ({{rowData.metadata.developerId}})</span>
+                        </div>
+                        <!-- Notes -->
+                        <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.295); white-space: pre-line;">
+                            <b>Notes:</b><br>
+                            <p-scrollPanel [style]="{width: '100%', height: '62px'}">
+                                <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+                            </p-scrollPanel>
+                        </div>
+                        <!-- Labels -->
+                        <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.295)">
+                            <b style="padding-bottom: 5px;">Labels:</b><br>
+                            <div *ngFor="let label of rowData['metadata']['labels']"
+                                style="display: inline-flex; margin-top: 5px;">
+                                <div style="padding: 2px 7px 3px 0px;">
+                                    <span style="background-color: rgba(80, 80, 80, 0.185); padding: 5px; border-radius: 3px;">{{label}}</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </td>
+            </tr>
+        </ng-template>
+
+    </p-table>
+
+    <!--download buttons for exporting table to either csv or excel file-->
+    <div *ngIf="loadTable" class="table_export_buttons_alignment">
+        <button pButton type="button" class="table_export_button" (click)="exportTable('csv')" matTooltip="Export Table to CSV" matTooltipPosition="above" style="width: 55px;">
+            <i class="pi pi-file" style="margin-top: 3px; margin-left: 4px;"></i>
+            <label style="font-weight: 800; margin-top: 1px;">CSV</label>
+        </button>
+        <button pButton type="button" class="table_export_button" (click)="exportTable('excel')" matTooltip="Export Table to XLSX" matTooltipPosition="above" style="width: 65px; background-color: green;">
+            <i class="pi pi-file-excel" style="margin-top: 3px; margin-left: 4px;"></i>
+            <label style="font-weight: 800; margin-top: 1px">Excel</label>
+        </button>
+    </div>
+
+    <!-- Dialog to Change an MS Instance -->
+    <app-ms-instance-add *ngIf="showAddChangeMsInstance" [visible]="showAddChangeMsInstance" [msName]="msName" [msInstanceChange]="msInstanceChange" [currentRow]="currentRow" (handler)="addChangeMsInstance($event)"></app-ms-instance-add>
+
+    <!-- Dialog to Add a Component Spec -->
+    <app-comp-spec-add *ngIf="showCsAddDialog" [visible]="showCsAddDialog" (handler)="addNewCs($event)"></app-comp-spec-add>
+
+    <!--Pop-up for "Success" changing MS Instance-->
+    <p-toast key="changeSuccess"></p-toast>
+    <!--Pop-up for "Success" adding Component Spec-->
+    <p-toast key="compSpecAdded" [style]="{width: '400px'}"></p-toast>
+    <!--Pop-up for "Error" adding Component Spec-->
+    <p-toast class="toast-newline" key="errorOnCsAdd" [style]="{width: '700px'}"></p-toast>
+    
+    <p-toast class="toast-newline" key="csViewError" [style]="{width: '700px'}"></p-toast>
+
+    <p-toast key="bpGenMessage" [style]="{width: '450px'}"></p-toast>
+    
+</div>
\ No newline at end of file
diff --git a/mod2/ui/src/app/msInstances/msInstances.component.spec.ts b/mod2/ui/src/app/msInstances/msInstances.component.spec.ts
new file mode 100644 (file)
index 0000000..5a26b58
--- /dev/null
@@ -0,0 +1,127 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatMenuModule, MatTooltipModule } from '@angular/material';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { MessageService } from 'primeng/api';
+import { ButtonModule } from 'primeng/button';
+import { CalendarModule } from 'primeng/calendar';
+import { DialogModule } from 'primeng/dialog';
+import { DropdownModule } from 'primeng/dropdown';
+import { ScrollPanelModule } from 'primeng/scrollpanel';
+import { TableModule } from 'primeng/table';
+import { ToastModule } from 'primeng/toast';
+import { CompSpecAddComponent } from '../comp-spec-add/comp-spec-add.component';
+import { MsInstanceAddComponent } from '../ms-instance-add/ms-instance-add.component';
+
+import { MsInstancesComponent } from './msInstances.component';
+
+describe('MsInstancesComponent', () => {
+  let component: MsInstancesComponent;
+  let fixture: ComponentFixture<MsInstancesComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        MsInstancesComponent,
+        CompSpecAddComponent,
+        MsInstanceAddComponent
+      ],
+      imports: [
+        Ng4LoadingSpinnerModule,
+        TableModule,
+        MatMenuModule,
+        ScrollPanelModule,
+        ToastModule,
+        DialogModule,
+        DropdownModule,
+        FormsModule,
+        ReactiveFormsModule,
+        ButtonModule,
+        CalendarModule,
+        HttpClientTestingModule,
+        ToastModule,
+        RouterTestingModule,
+        MatTooltipModule
+      ],
+      providers: [
+        MessageService,
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MsInstancesComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it(`should fill msInstances Object`, () => {
+    const fixture = TestBed.createComponent(MsInstancesComponent);
+    const app = fixture.debugElement.componentInstance;
+
+    let mockMsInstance = [{
+      id: 'testId1234',
+      name: 'test-MS',
+      release: '2008',
+      version: '1.0.0',
+      status: 'New',
+      msInfo: {
+        id: 'testBaseMsId1234',
+        name: 'test Base Ms',
+        tag: 'test-MS-tag',
+      },
+      metadata: {
+        scrumLead: 'test',
+        scrumLeadId: 'testId',
+        systemsEngineer: 'test',
+        systemsEngineerId: 'testId',
+        developer: 'test',
+        developerId: 'testId',
+        pstDueDate: '01-01-2020 12:00',
+        pstDueIteration: '1.1',
+        eteDueDate: '01-01-2020 12:00',
+        eteDueIteration: '1.1',
+        createdBy: 'test',
+        createdOn: '01-01-2020 12:00',
+        updatedBy: 'test',
+        updatedOn: '01-01-2020 12:00',
+        notes: 'test',
+        labels: ['test'],
+      },
+      activeSpec: 'test'
+    }]
+
+    app.fillTable(mockMsInstance)
+
+    expect(app.loadTable).toEqual(true);
+    expect(app.msInstances.length).toEqual(1);
+  });
+});
diff --git a/mod2/ui/src/app/msInstances/msInstances.component.ts b/mod2/ui/src/app/msInstances/msInstances.component.ts
new file mode 100644 (file)
index 0000000..e011cb9
--- /dev/null
@@ -0,0 +1,511 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
+import { MatTableDataSource } from '@angular/material/table';
+import { Table } from 'primeng/table';
+import { MessageService } from 'primeng/api';
+import { trigger, state, style, transition, animate } from '@angular/animations';
+import { MicroserviceInstanceService } from '../services/microservice-instance.service';
+import { DatePipe } from '@angular/common';
+import { DeploymentArtifactService } from '../services/deployment-artifact.service';
+import { CompSpecAddService } from '../services/comp-spec-add.service';
+import { BreadcrumbService } from '../services/breadcrumb.service';
+import { Router } from '@angular/router';
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+import { DownloadService } from '../services/download.service';
+
+@Component({
+  selector: 'app-msInstances',
+  templateUrl: './msInstances.component.html',
+  styleUrls: ['./msInstances.component.css'],
+  animations: [
+    trigger('rowExpansionTrigger', [
+      state('void', style({
+        transform: 'translateX(-10%)',
+        opacity: 0
+      })),
+      state('active', style({
+        transform: 'translateX(0)',
+        opacity: 1
+      })),
+      transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+    ])
+  ],
+  providers: [DatePipe]
+})
+export class MsInstancesComponent implements OnInit {
+  @ViewChild(Table, { static: false }) dt: Table;
+  @ViewChild('myInput', { static: false }) myInputVariable: ElementRef;
+  
+
+  /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+  msInstances: msInstance[] = [];
+  expandedItems: Array<any> = new Array<any>();
+  dataSource = new MatTableDataSource<msInstance>(this.msInstances);
+  cols: any[] = [
+    { field: 'baseMsName', header: 'MS Name' },
+    { field: 'tag', header: 'MS Tag' },
+    { field: 'release', header: 'Release', width: '7%' },
+    { field: 'pstDueDate', header: 'PST Date', width: '9%' },
+    { field: 'pstDueIteration', header: 'PST Iteration', width: '6.5%' },
+    { field: 'eteDueDate', header: 'ETE Date', width: '9%' },
+    { field: 'eteDueIteration', header: 'ETE Iteration', width: '6.5%' },
+    { field: 'status', header: 'Status', width: '125px' }
+  ];
+  selectedMsInstances: msInstance[] = [];
+  columns: any[];
+  loadTable: boolean;
+  filteredRows: any;
+  downloadItems: { label: string; command: () => void; }[];
+  showAddChangeMsInstance: boolean;
+  currentRow: any;
+  msInstanceChange: string = "change";
+  generatedBPs: any[] = [];
+  canGenerateSelectedBPs: boolean = false;
+  generateSelectedBPsTooltip: string = '';
+
+  // Json to add CS (Component Spec) to DB, returned from child
+  csAddJson: any;
+
+  showCsAddDialog: boolean = false;
+
+  showViewCs: boolean =false;
+  msInstanceId: string = '';
+  errorList: string[];
+
+  constructor(private spinnerService: Ng4LoadingSpinnerService, private msInstanceApi: MicroserviceInstanceService,
+              private bpApis: DeploymentArtifactService, private addCsApi: CompSpecAddService, private messageService: MessageService,
+              private datePipe: DatePipe, private router: Router, private downloadService: DownloadService, private bread: BreadcrumbService) { }
+
+  ngOnInit() {
+
+    this.getAllInstances();
+    
+  }
+
+  getAllInstances() {
+    this.spinnerService.show();
+    this.msInstances = [];
+    this.loadTable = false;
+    
+    this.msInstanceApi.getAllMsInstances()
+      .subscribe((data: any[]) => {
+        this.fillTable(data)
+      })
+
+    this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field }));
+  }
+
+
+  // * * * * * Show the Dialog to Change an MS Instance (<app-ms-instance-add> tag in the html) * * * * *
+  showAddChangeDialog(rowData) {
+      this.msInstanceId = rowData['id']  
+      this.showAddChangeMsInstance = true;
+      this.currentRow = rowData;
+  }
+
+    /* * * * Call API to Change an MS Instance * * * */
+  addChangeMsInstance(jsonFromChildDialog) {
+    if (jsonFromChildDialog === null) {
+          this.showAddChangeMsInstance = false;
+      } else {
+          this.msInstanceApi.addChangeMsInstance("CHANGE", this.msInstanceId, jsonFromChildDialog).subscribe(
+              (data) => {
+                this.updateCurrentRow(data);  
+                this.messageService.add({ key: 'changeSuccess', severity: 'success', summary: 'Success', detail: "MS Instance Updated", life: 5000 });
+                this.showAddChangeMsInstance = false;
+              },
+              (errResponse) => {
+                if (errResponse.error.message) {
+                  this.messageService.add({ key: 'instanceAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.message, sticky: true });
+                } else {
+                  this.messageService.add({ key: 'instanceAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.status, sticky: true });
+                }
+              }
+          )
+      }
+  }
+
+  updateCurrentRow(responseData) {
+    const newRow = responseData;
+    this.currentRow['release']                       = newRow['release'];
+    this.currentRow['metadata']['scrumLead']         = newRow['metadata']['scrumLead'];
+    this.currentRow['metadata']['scrumLeadId']       = newRow['metadata']['scrumLeadId'];
+    this.currentRow['metadata']['systemsEngineer']   = newRow['metadata']['systemsEngineer'];
+    this.currentRow['metadata']['systemsEngineerId'] = newRow['metadata']['systemsEngineerId'];
+    this.currentRow['metadata']['developer']         = newRow['metadata']['developer'];
+    this.currentRow['metadata']['developerId']       = newRow['metadata']['developerId'];
+    this.currentRow['pstDueDate']                    = this.datePipe.transform(newRow['metadata']['pstDueDate'], 'yyyy-MM-dd');
+    this.currentRow['pstDueIteration']               = newRow['metadata']['pstDueIteration'];
+    this.currentRow['eteDueDate']                    = this.datePipe.transform(newRow['metadata']['eteDueDate'], 'yyyy-MM-dd');
+    this.currentRow['eteDueIteration']               = newRow['metadata']['eteDueIteration'];
+    this.currentRow['metadata']['labels']            = newRow['metadata']['labels'];
+    this.currentRow['metadata']['notes']             = newRow['metadata']['notes'];
+    this.currentRow['metadata']['updatedBy']         = newRow['metadata']['updatedBy'];
+    this.currentRow['metadata']['updatedOn']         = this.datePipe.transform(newRow['metadata']['updatedOn'], 'MM-dd-yyyy HH:mm');
+  }
+
+  // * * * * * Show the Dialog to Add a CS (in the html) * * * * *
+  // * * * * * Store the MS Instance ID for the URL and the "current row" to update when a CS is saved * * * * *
+  showAddCSDialog(rowData) {
+    this.showCsAddDialog = true;
+    this.msInstanceId = rowData['id'];
+    this.currentRow = rowData;
+  }
+
+  // * * * * * Add a CS * * * * *
+  addNewCs(jsonFromChildDialog) {
+    let compSpecAddMessage = '';
+    if (jsonFromChildDialog) {
+      this.csAddJson = jsonFromChildDialog;
+      if((JSON.parse(this.csAddJson)).policyJson === null){
+        compSpecAddMessage = 'Component Spec Added';
+      } else {
+        console.log("here")
+        compSpecAddMessage = 'Component Spec and Policy added '
+      }
+      
+      this.addCsApi.addCsToCatalog(this.msInstanceId, this.csAddJson).subscribe(
+        (response: any) => {
+          this.messageService.add({ key: 'compSpecAdded', severity: 'success', summary: 'Success', detail: compSpecAddMessage, life: 5000 });
+          this.showCsAddDialog = false;
+          this.currentRow['activeSpec'] = true;
+        },
+        errResponse => {
+            if (errResponse.error.errors) {
+                this.messageService.add({ key: 'errorOnCsAdd', severity: 'error', summary: errResponse.error.message, detail: errResponse.error.errors.join('\n'), sticky: true});
+            } else {
+                let summary = errResponse.error.status + " - " + errResponse.error.error;
+                this.messageService.add({ key: 'errorOnCsAdd', severity: 'error', summary: summary, detail: errResponse.error.message, sticky: true});
+            }
+         });
+    } else {
+        this.showCsAddDialog = false
+    };
+  }
+
+/* * * * View Component Specs 
+msName: string;
+msRelease: string;
+  showViewCsDialog(data){
+    this.msInstanceId = data['id']
+    this.msName = data['name']
+    this.msRelease = data['release']
+    this.showViewCs = true;
+  }
+  csView(data){
+    if(data===null){
+      this.showViewCs = false
+    } else {
+      this.showViewCs = false
+      this.messageService.add({ key: 'csViewError', severity: 'error', summary: 'Error Message', detail: data.error.message, sticky: true });    
+    }
+  } 
+  * * * */
+
+  /* * * * Generate single blueprint * * * */
+  generateBlueprints(data){
+    this.bpApis.postBlueprint(data['id']).subscribe((response) => { 
+      this.messageService.add({ key: 'bpGenMessage', severity: 'success', summary: 'Success Message', detail: 'Blueprint Generated', life: 5000 });          
+    }, (errResponse) => { 
+      this.messageService.add({ key: 'bpGenMessage', severity: 'error', summary: 'Error Message', detail: errResponse.error.message, life: 15000 });          
+    })
+  }
+
+  /* * * * Check if generate selected blueprints button should be disabled and set tooltip message * * * */
+  checkCanGenerateBp() {
+    if (this.selectedMsInstances.length > 0) {
+
+      let noActiveSpecs: string[] = [];
+      let checkReleases: boolean = true;
+      let firstRelease = this.selectedMsInstances[0]['release'];
+      for (let elem of this.selectedMsInstances) {
+        if (elem.release !== firstRelease){
+          checkReleases = false
+          this.canGenerateSelectedBPs = false
+          this.generateSelectedBPsTooltip = 'Cannot Generate Blueprints For Different Releases'
+          break
+        }
+        if (elem.activeSpec === null) {
+          noActiveSpecs.push(elem.name)
+          this.generateSelectedBPsTooltip += elem.name
+        }
+      }
+
+      if (noActiveSpecs.length < 1 && checkReleases) {
+        this.canGenerateSelectedBPs = true
+      } else if (noActiveSpecs.length > 0 && checkReleases){
+        this.canGenerateSelectedBPs = false
+        this.generateSelectedBPsTooltip = 'No Active Specs For :  '
+        let i: number = 1;
+        for (let elem of noActiveSpecs) {
+          if (i === noActiveSpecs.length) {
+            this.generateSelectedBPsTooltip += '{' + elem + '}'
+          } else {
+            this.generateSelectedBPsTooltip += '{' + elem + '}, '
+          }
+          i++
+        }
+      }
+    } else  {
+      this.canGenerateSelectedBPs = false
+      this.generateSelectedBPsTooltip = "No Instances Selected"
+    }
+  }
+  
+  /* * * * Generate multiple blueprint * * * */
+  successfulBpGens: number;
+  generateSelectedBlueprints(){
+    this.successfulBpGens = 0;
+    this.count = 0;
+    this.selectionLength = this.selectedMsInstances.length;
+
+    this.spinnerService.show();
+
+    (async () => {
+      for (let instance of this.selectedMsInstances) {
+        this.bpApis.postBlueprint(instance.id).subscribe((response) => {
+          this.bpGenSuccess()          
+        }, (errResponse) => {
+            this.bpGenError(errResponse.error.message)
+        })
+        await timeout(1500);
+      }
+    })();
+
+    function timeout(ms) {
+      return new Promise(resolve => setTimeout(resolve, ms));
+    }
+
+    this.selectedMsInstances = []
+  }
+
+  /* * * * For BP Gen Successes * * * */
+  selectionLength: number;
+  count: number;
+  bpGenSuccess(){
+    this.successfulBpGens++;
+    this.count++;
+    if (this.count === this.selectionLength){
+      if(this.successfulBpGens > 0){
+        this.messageService.add({ key: 'bpGenMessage', severity: 'success', summary: 'Success Message', detail: 'Blueprints Generated', life: 5000 });
+        this.spinnerService.hide()
+      }
+    }
+  }
+
+  /* * * * For BP Gen Errors * * * */
+  bpGenError(err){
+    this.count++;
+    if (this.count === this.selectionLength) {
+      if (this.successfulBpGens > 0) {
+        this.messageService.add({ key: 'bpGenMessage', severity: 'success', summary: 'Success Message', detail: 'Blueprints Generated', life: 5000 });
+        this.spinnerService.hide()
+      }
+    }
+    this.messageService.add({ key: 'bpGenMessage', severity: 'error', summary: 'Error Message', detail: err, life: 15000 });
+  }
+
+  /* * * * View the Blueprints for the selected MS Instance * * * */
+  viewBlueprints(rowData) {
+    this.router.navigate(["blueprints"], {queryParams:{tag: rowData['tag'], release:rowData['release'] }});
+    this.bread.setBreadcrumbs("Blueprints", "add");
+  }
+
+  /* * * * View the Component Spec for the selected MS Instance * * * */
+  viewCompSpecs(rowData) {
+    this.router.navigate(["CompSpecs"], { queryParams: { instanceId: rowData['id'] }});
+    this.bread.setBreadcrumbs("Component Specs", "add");
+  }
+
+  /* * * * Stores filtered data in new array * * * */
+  onTableFiltered(values) {
+    if (values) { this.filteredRows = values; }
+    else { this.filteredRows = this.msInstances; }
+  }
+
+  /* * * * Export ms instance table to excel or csv * * * */
+  exportTable(exportTo) {
+    let downloadElements: any[] = []
+
+    //labels array not handled well by excel download so converted them to a single string
+    for(let row of this.filteredRows){
+      let labels;
+      let notes;
+      if(exportTo === "excel"){
+        if(row.metadata.labels !== undefined){
+          labels = row.metadata.labels.join(",")
+        }
+      } else {
+        labels = row.metadata.labels
+      }
+
+      if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') {
+        notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n")
+      }
+
+      downloadElements.push({
+        MS_Name: row.name,
+        MS_Tag: row.tag,
+        Release: row.release,
+        PST_Due_Date: row.pstDueDate,
+        PST_Due_Iteration: row.pstDueIteration,
+        ETE_Due_Date: row.eteDueDate,
+        ETE_Due_Iteration: row.eteDueIteration,
+        Status: row.status,
+        Created_By: row.metadata.createdBy,
+        Created_On: row.metadata.createdOn,
+        Updated_By: row.metadata.updatedBy,
+        Updated_On: row.metadata.updatedOn,
+        Scrum_Lead: row.metadata.scrumLead,
+        Scrum_Lead_Id: row.metadata.scrumLeadId,
+        Systems_Engineer: row.metadata.systemsEngineer,
+        Systems_Engineer_Id: row.metadata.systemsEngineerId,
+        Developer: row.metadata.developer,
+        Developer_Id: row.metadata.developerId,
+        Notes: notes,
+        Labels: labels
+      })
+    }
+
+    let csvHeaders = [];
+
+    if (exportTo === "csv") {
+      csvHeaders = [
+        "MS_Name", 
+        "MS_Tag", 
+        "Release", 
+        "PST_Due_Date", 
+        "PST_Due_Iteration", 
+        "ETE_Due_Date", 
+        "ETE_Due_Iteration", 
+        "Status", 
+        "Created_By", 
+        "Created_On", 
+        "Updated_By", 
+        "Updated_On", 
+        "Scrum_Lead", 
+        "Scrum_Lead_Id", 
+        "Systems_Engineer", 
+        "Systems_Engineer_Id", 
+        "Developer", 
+        "Developer_Id", 
+        "Notes", 
+        "Labels"
+      ];
+    }
+
+    this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders)
+  }
+  
+    /* * * * Fill ms instance table * * * */
+    fillTable(data) {
+
+        for (let elem of data) {
+
+          /* * * Now storing as dates (not strings) on DB, so need to convert old data (mm-dd-yyyy and m-d-yyyy) * * */
+          let pstDueDate: any;
+          if  (elem.metadata.pstDueDate && (elem.metadata.pstDueDate.length <= 11 &&
+                                                        elem.metadata.pstDueDate.length > 7)) {
+              pstDueDate = new Date(elem.metadata.pstDueDate.replace(/-/g, '/'))  // dash is invalid date format, FF fails
+              pstDueDate = this.datePipe.transform(pstDueDate, 'yyyy-MM-dd')
+          } else if (elem.metadata.pstDueDate) {
+              pstDueDate = this.datePipe.transform(elem.metadata.pstDueDate, 'yyyy-MM-dd')
+          } else {
+              pstDueDate = elem.metadata.pstDueDate
+          }
+
+          let eteDueDate: any;
+          if  (elem.metadata.eteDueDate && (elem.metadata.eteDueDate.length <= 11 &&
+                                                        elem.metadata.eteDueDate.length > 7)) {
+              eteDueDate = new Date(elem.metadata.eteDueDate.replace(/-/g, '/'))  // dash is invalid date format, FF fails
+              eteDueDate = this.datePipe.transform(eteDueDate, 'yyyy-MM-dd')
+          } else if (elem.metadata.eteDueDate) {
+              eteDueDate = this.datePipe.transform(elem.metadata.eteDueDate, 'yyyy-MM-dd')
+          } else {
+              eteDueDate = elem.metadata.eteDueDate
+          }
+
+            var tempElem: msInstance = {
+                id:         elem.id,
+                name:       elem.name,
+                tag:        elem.msInfo.tag,
+                release:    elem.release,
+                version:    elem.version,
+                status:     elem.status,
+                baseMsId:   elem.msInfo.id,
+                baseMsName: elem.msInfo.name,
+                metadata: {
+                    scrumLead:         elem.metadata.scrumLead,
+                    scrumLeadId:       elem.metadata.scrumLeadId,
+                    systemsEngineer:   elem.metadata.systemsEngineer,
+                    systemsEngineerId: elem.metadata.systemsEngineerId,
+                    developer:         elem.metadata.developer,
+                    developerId:       elem.metadata.developerId,
+                    createdBy:         elem.metadata.createdBy,
+                    createdOn:         this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'),
+                    updatedBy:         elem.metadata.updatedBy,
+                    updatedOn:         this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm'),
+                    notes:             elem.metadata.notes,
+                    labels:            elem.metadata.labels,
+                },
+                pstDueDate:      pstDueDate,
+                pstDueIteration: elem.metadata.pstDueIteration,
+                eteDueDate:      eteDueDate,
+                eteDueIteration: elem.metadata.eteDueIteration,
+                activeSpec:      elem.activeSpec
+            }
+            this.msInstances.push(tempElem)
+        }
+        
+        this.filteredRows = this.msInstances
+        this.loadTable = true;    
+        this.spinnerService.hide()
+    }
+}
+
+export interface msInstance{
+  id: string,
+  name: string,
+  tag: string,
+  release: string,
+  version: string,
+  status: string,
+  baseMsId: string, 
+  baseMsName: string ,
+  metadata: {
+    scrumLead: string,
+    scrumLeadId: string,
+    systemsEngineer: string,
+    systemsEngineerId: string,
+    developer: string,
+    developerId: string,
+    createdBy: string,
+    createdOn: string,
+    updatedBy: string,
+    updatedOn: string,
+    notes: string,
+    labels: string[],
+  }
+  pstDueDate: string,
+  pstDueIteration: string,
+  eteDueDate: string,
+  eteDueIteration: string,
+  activeSpec: any
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css
new file mode 100644 (file)
index 0000000..1130851
--- /dev/null
@@ -0,0 +1,27 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+iframe{
+    padding-top: 0px;
+    border: none;
+}
+
+#content{
+    position:absolute; 
+    left: 0; right: 0; bottom: 0; top: 0px; 
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html
new file mode 100644 (file)
index 0000000..417ee39
--- /dev/null
@@ -0,0 +1,23 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<ng4-loading-spinner id=loadingSpinner [timeout]="60000"></ng4-loading-spinner>
+<div id="content">
+    <iframe width="100%" height="98.5%" [src]="video | safe" onload="document.getElementById('loadingSpinner').style.display='none';"></iframe>
+</div>
+
diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts
new file mode 100644 (file)
index 0000000..d88c06d
--- /dev/null
@@ -0,0 +1,48 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
+import { SafePipe } from '../onboarding-tools/onboarding-tools.component';
+
+import { OnboardingToolsComponent } from './onboarding-tools.component';
+
+describe('OnboardingToolsComponent', () => {
+  let component: OnboardingToolsComponent;
+  let fixture: ComponentFixture<OnboardingToolsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [OnboardingToolsComponent, SafePipe],
+      imports: [
+        Ng4LoadingSpinnerModule,
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(OnboardingToolsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts
new file mode 100644 (file)
index 0000000..d400120
--- /dev/null
@@ -0,0 +1,52 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, ViewEncapsulation, ViewChild, ElementRef, PipeTransform, Pipe, OnInit } from '@angular/core';
+import { DomSanitizer } from "@angular/platform-browser";
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+import { environment } from '../../environments/environment';
+
+@Pipe({ name: 'safe' })
+export class SafePipe implements PipeTransform {
+  constructor(private sanitizer: DomSanitizer) { }
+  transform(url) {
+    return this.sanitizer.bypassSecurityTrustResourceUrl(url);
+  }
+}
+
+@Component({
+  selector: 'app-onboarding-tools',
+  templateUrl: './onboarding-tools.component.html',
+  styleUrls: ['./onboarding-tools.component.css']
+})
+
+export class OnboardingToolsComponent implements OnInit {
+
+  title = 'Onboarding Tools';
+
+  //video: string = `http://${environment.api_baseURL}:30991/onboarding-toolbox/blueprint-generator`
+  video: string = 'http://dcae-onboarding-toolbox-fe.ecomp.idns.cip.att.com:30991/onboarding-toolbox/blueprint-generator'
+
+  constructor(private spinnerService: Ng4LoadingSpinnerService) { 
+    this.spinnerService.show();
+  }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/mod2/ui/src/app/register/register.component.css b/mod2/ui/src/app/register/register.component.css
new file mode 100644 (file)
index 0000000..730a52d
--- /dev/null
@@ -0,0 +1,18 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
diff --git a/mod2/ui/src/app/register/register.component.html b/mod2/ui/src/app/register/register.component.html
new file mode 100644 (file)
index 0000000..74f9347
--- /dev/null
@@ -0,0 +1,62 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<p-card header="Register a New User" [style]="{margin:'100px 200px 100px 200px'}" class="ui-card">
+    <hr class="line-break">
+    <form [formGroup]="form" class="p-5 bg-faded" style="margin-left: 250px;margin-bottom: 100px;">
+        <div class="ui-g ui-fluid">
+            <div class="ui-g-12 ui-md-6">
+                <div class="ui-inputgroup" >
+                    <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+                    <input type="text" pInputText placeholder="ATT UID" formControlName="username" class="ui-inputtext">
+                </div>
+            </div>
+        </div>
+        <i *ngIf="form.get('username').errors && form.get('username').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">username should be at least 5 characters</i>
+        <i *ngIf="form.get('username').errors && form.get('username').errors.maxlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">username should be at most 10 characters</i>
+        <div class="ui-g ui-fluid" >
+            <div class="ui-g-12 ui-md-6">
+                <div class="ui-inputgroup" >
+                    <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+                    <input type="text" pInputText placeholder="Full Name" formControlName="fullName" class="ui-inputtext">         
+                </div>
+            </div>
+        </div>
+        <div class="ui-g ui-fluid" >
+            <div class="ui-g-12 ui-md-6">
+                <div class="ui-inputgroup">  
+                <button pButton type="button" icon="pi pi-refresh" class="ui-button-warn" (click)="generateNewPassword()" ></button>
+                <input type="text" pInputText formControlName="password" placeholder="Generate password" class="ui-inputtext">
+                </div>
+            </div>
+        </div>          
+        <i *ngIf="form.get('password').errors && form.get('password').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">password should be at least 6 characters</i>
+        <div class="ui-g ui-fluid" >
+            <div class="ui-g-12 ui-md-6">
+              <div class="ui-inputgroup" >
+                <span class="ui-inputgroup-addon"><i class="pi pi-users" style="line-height: 1.25; height: 1em;"></i></span>
+                <p-multiSelect [options]="rolesFromBackend" formControlName="roles" [showToggleAll]="true" [virtualScroll]="true" [filter]="false" [style]="{height:'3em'}" class="ui-multiselect"></p-multiSelect>  
+              </div> 
+            </div>
+        </div>     
+        <p-footer class="text-left ui-g-12">
+            <button pButton type="button" class="ui-button-info" label="Cancel" (click)="cancel()" style="margin-right: .25em"></button>
+            <button pButton type="submit" class="ui-button-success" label="Register" [disabled]="!form.valid" (click)="submit()"></button>
+        </p-footer>
+    </form>
+</p-card>
diff --git a/mod2/ui/src/app/register/register.component.spec.ts b/mod2/ui/src/app/register/register.component.spec.ts
new file mode 100644 (file)
index 0000000..d05fcd1
--- /dev/null
@@ -0,0 +1,61 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { CardModule } from 'primeng/card';
+import { MultiSelectModule } from 'primeng/multiselect';
+
+import { RegisterComponent } from './register.component';
+
+describe('RegisterComponent', () => {
+  let component: RegisterComponent;
+  let fixture: ComponentFixture<RegisterComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [RegisterComponent],
+      imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        MultiSelectModule,
+        CardModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(RegisterComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/register/register.component.ts b/mod2/ui/src/app/register/register.component.ts
new file mode 100644 (file)
index 0000000..28b0677
--- /dev/null
@@ -0,0 +1,99 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit } from '@angular/core';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { AuthService } from '../services/auth.service';
+import { Router } from '@angular/router';
+import { User } from '../models/User';
+import { AuthResponse } from '../models/AuthResponse';
+import {SelectItem} from 'primeng/api';
+import { UserService } from '../services/user.service';
+
+@Component({
+  selector: 'app-register',
+  templateUrl: './register.component.html',
+  styleUrls: ['./register.component.css']
+})
+export class RegisterComponent implements OnInit {
+
+  user: User = {
+    username:'',
+    fullName:'',
+    password:'',
+    roles: []
+  };
+  form: FormGroup;
+  roleOptions: SelectItem[];
+  rolesFromBackend = [];
+
+  constructor(private fb: FormBuilder, private authService: AuthService, private router: Router, private userService: UserService) { }
+
+  ngOnInit() {
+    this.form = this.fb.group({
+        username: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(10)]],
+        fullName: ['', [Validators.required]],
+        password: ['', [Validators.required, Validators.minLength(6)]],
+        roles:['', [Validators.required]]
+    });
+    // this.roleOptions = [{label:'Admin', value:{name:'ROLE_ADMIN'}}, {label:'User', value:{name:'ROLE_USER'}}, {label:'ScrumLead', value:{name:'ROLE_SCRUMLEAD'}}, {label:'Developer', value:{name:'ROLE_DEVELOPER'}}, {label:'PST', value:{name:'ROLE_PST'}}, {label:'ETE', value:{name:'ROLE_ETE'}}, {label:'Ops', value:{name:'ROLE_OPS'}}];
+    this.userService.getRoles().subscribe(res=>{
+     Object.values(res).forEach(ele=>{
+      this.rolesFromBackend.push({label:ele.substring(5), value:ele});
+     });
+    });
+  }
+
+  generateNewPassword() {
+    this.form.value.password = '';
+    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&';
+    var array = new Uint32Array(32);
+    window.crypto.getRandomValues(array);
+    for(let i=0;i<32;i++) {
+      const index = Math.floor(array[i] % chars.length);
+      this.form.value.password += chars.charAt(index);
+    }
+    this.form.patchValue({password: this.form.value.password});
+  }
+
+  cancel() {
+    const result = window.confirm("Are you sure to quit registering current user and go back to user management?");
+    if(result === true){
+      this.router.navigate(['/users']);
+    }  
+  }
+
+  submit() {
+    this.user.fullName = this. form.value.fullName;
+    this.user.username = this.form.value.username;
+    this.user.password = this.form.value.password;
+    this.user.roles = this.form.value.roles;
+    console.log(this.user.roles);
+    this.authService.register(this.user) 
+    .subscribe( (res: AuthResponse) => {
+          alert(res.message);
+          this.router.navigate(['/users']);
+      }, (err) => {
+        console.log(err);
+        alert(err.error.message);
+        this.form.reset();
+      }
+  ); 
+  }
+
+}
diff --git a/mod2/ui/src/app/reset-password/reset-password.component.css b/mod2/ui/src/app/reset-password/reset-password.component.css
new file mode 100644 (file)
index 0000000..730a52d
--- /dev/null
@@ -0,0 +1,18 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
diff --git a/mod2/ui/src/app/reset-password/reset-password.component.html b/mod2/ui/src/app/reset-password/reset-password.component.html
new file mode 100644 (file)
index 0000000..748216a
--- /dev/null
@@ -0,0 +1,64 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<p-card header="Password Reset" [style]="{margin:'100px 300px 500px 300px'}">
+    <hr class="line-break">
+    <form [formGroup]="form" (ngSubmit)="submit()" class="p-5 bg-faded" style="margin-left: 200px;margin-bottom: 100px;">
+        <div class="ui-g ui-fluid" >
+            <div class="ui-g-12 ui-md-4">
+                <div class="ui-inputgroup" >
+                    <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+                    <input type="text" pInputText placeholder="User id" formControlName="id" >         
+                </div>
+            </div>
+        </div>
+        <div *ngIf="!authService.isAdmin; else admin">
+        <div class="ui-g ui-fluid">
+            <div class="ui-g-12 ui-md-4">
+                <div class="ui-inputgroup" >
+                    <span class="ui-inputgroup-addon"><i class="pi pi-key" style="line-height: 1.25;"></i></span>
+                    <input type="text" pInputText placeholder="New Password" formControlName="newPassword">         
+                </div>
+            </div>
+        </div>
+        <div class="ui-g ui-fluid">
+            <div class="ui-g-12 ui-md-4">
+                <div class="ui-inputgroup" >
+                    <span class="ui-inputgroup-addon"><i class="pi pi-key" style="line-height: 1.25;"></i></span>
+                    <input type="text" pInputText placeholder="Confirm Password" formControlName="confirm_newPassword">         
+                </div>
+            </div>
+        </div>
+        </div>
+        <ng-template class="ui-g ui-fluid" #admin>
+            <div class="ui-g-12 ui-md-4">
+                <div class="ui-inputgroup">
+                <input type="text" pInputText formControlName="password" placeholder="generate password">   
+                <button pButton type="button" icon="pi pi-refresh" class="ui-button-warn" (click)="generateNewPassword()" ></button>      
+                </div>
+            </div>
+        </ng-template>
+        <p-footer class="text-left ui-g-12">
+            <button pButton type="button" class="ui-button-info" label="Cancel" (click)="cancel()" style="margin-right: .25em"></button>
+            <button pButton type="submit" class="ui-button-success" label="Login" [disabled]="!form.valid"></button>
+        </p-footer>
+        <div class="text-left ui-g-12">
+            <a routerLink="/register">Not a registered user? Click here to register now!</a> 
+        </div>
+      </form>
+    </p-card>
diff --git a/mod2/ui/src/app/reset-password/reset-password.component.spec.ts b/mod2/ui/src/app/reset-password/reset-password.component.spec.ts
new file mode 100644 (file)
index 0000000..a26d90b
--- /dev/null
@@ -0,0 +1,58 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { CardModule } from 'primeng/card';
+
+import { ResetPasswordComponent } from './reset-password.component';
+
+describe('ResetPasswordComponent', () => {
+  let component: ResetPasswordComponent;
+  let fixture: ComponentFixture<ResetPasswordComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ResetPasswordComponent],
+      imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        CardModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ResetPasswordComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/reset-password/reset-password.component.ts b/mod2/ui/src/app/reset-password/reset-password.component.ts
new file mode 100644 (file)
index 0000000..b704b40
--- /dev/null
@@ -0,0 +1,78 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit } from '@angular/core';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { AuthService } from '../services/auth.service';
+import { Router } from '@angular/router';
+import { User } from '../models/User';
+
+@Component({
+  selector: 'app-reset-password',
+  templateUrl: './reset-password.component.html',
+  styleUrls: ['./reset-password.component.css']
+})
+export class ResetPasswordComponent implements OnInit {
+
+  form: FormGroup;
+
+  constructor(private fb: FormBuilder, public authService: AuthService, private router: Router) { }
+
+  ngOnInit() {
+    this.form = this.fb.group({
+        id: ['', [Validators.required]],
+        password: '',
+        newPassword: '',
+        confirm_newPassword: ''
+    });
+  }
+
+  cancel() {
+    this.form.reset();
+  }
+
+  generateNewPassword() {
+    this.form.value.password = '';
+    let p = '';
+    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&';
+    for(let i=1;i<12;i++) {
+      const index = Math.floor(Math.random() * chars.length + 1);
+      this.form.value.password += chars.charAt(index);
+    }
+    this.form.patchValue({password: this.form.value.password});
+  }
+
+  submit() {
+    const id = this.form.value.id;
+    if(!this.authService.isAdmin){
+    if(this.form.value.newPassword === this.form.value.confirm_newPassword){
+      const password = this.form.value.newPassword;
+      console.log(id);
+      console.log(password); // toJane: need to call user API
+    } else {
+      alert('Your passwords do not match, please re-confirm!');
+      this.form.patchValue({newPassword: '', confirm_newPassword: ''});
+    }
+  } else {
+     const password = this.form.value.password;
+     console.log(id);
+     console.log(password);// toJane: need to call user API
+  }
+}
+
+}
diff --git a/mod2/ui/src/app/services/auth.service.spec.ts b/mod2/ui/src/app/services/auth.service.spec.ts
new file mode 100644 (file)
index 0000000..eecc055
--- /dev/null
@@ -0,0 +1,43 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { AuthService } from './auth.service';
+
+describe('AuthService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ],
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule
+      ]
+    });
+  });
+  it('should be created', () => {
+    const service: AuthService = TestBed.get(AuthService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/auth.service.ts b/mod2/ui/src/app/services/auth.service.ts
new file mode 100644 (file)
index 0000000..49bb5f1
--- /dev/null
@@ -0,0 +1,95 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { tap } from 'rxjs/internal/operators';
+import { User } from '../models/User';
+import { JwtHelperService } from '@auth0/angular-jwt';
+import { AuthResponse } from '../models/AuthResponse';
+import { Router } from '@angular/router';
+import { environment } from '../../environments/environment';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AuthService{
+
+ private user: User = {
+   username: '',
+   roles:[]
+ };
+ authPass=false;
+ isAdmin=false;
+ reLoginMsg = false;
+  constructor(
+    private http:HttpClient,
+    private jwtHelper: JwtHelperService,
+    private router: Router
+    ) { 
+      
+    }
+
+  register(user: User): Observable<AuthResponse> {
+    return this.http.post<AuthResponse>(`http://${environment.api_baseURL}:31003/api/auth/signup`, user);
+    // return this.http.post<AuthResponse>(`http://localhost:8082/api/auth/signup`, user);
+
+  }
+
+  login(user: User): Observable<AuthResponse> {
+      return this.http.post<AuthResponse>(`http://${environment.api_baseURL}:31003/api/auth/signin`, user)
+      .pipe(
+       tap((res: AuthResponse) => {
+        localStorage.setItem('token', res.token);
+        this.setUser();
+        this.authPass = true;
+       })
+      );
+  }
+
+  setUser() {
+    this.user.username = this.jwtHelper.decodeToken(localStorage.getItem('token')).sub;
+    this.user.roles = this.jwtHelper.decodeToken(localStorage.getItem('token')).roles;
+    this.user.fullName = this.jwtHelper.decodeToken(localStorage.getItem('token')).fullName;
+    
+  }
+
+  checkLogin(): Observable<boolean>{
+    return this.http.post<boolean>(`http://${environment.api_baseURL}:31003/api/auth/validate-token`, null);
+  }
+
+  getJwt() {
+    if(localStorage.getItem('token')){
+      return localStorage.getItem('token');
+    }
+    return "";
+  }
+
+  logout() {
+    localStorage.removeItem('token');
+    this.authPass = false;
+    this.router.navigate(['/login']);
+  }
+
+  getUser(): User {
+    return this.user;
+  }
+
+}
diff --git a/mod2/ui/src/app/services/base-microservice.service.spec.ts b/mod2/ui/src/app/services/base-microservice.service.spec.ts
new file mode 100644 (file)
index 0000000..1cef5bd
--- /dev/null
@@ -0,0 +1,42 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { BaseMicroserviceService } from './base-microservice.service';
+
+describe('BaseMicroserviceService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+
+  it('should be created', () => {
+    const service: BaseMicroserviceService = TestBed.get(BaseMicroserviceService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/base-microservice.service.ts b/mod2/ui/src/app/services/base-microservice.service.ts
new file mode 100644 (file)
index 0000000..a5c64b1
--- /dev/null
@@ -0,0 +1,35 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { environment } from '../../environments/environment';
+import { HttpClient } from '@angular/common/http';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class BaseMicroserviceService {
+
+  private url: string = `http://${environment.api_baseURL}:31001/api/`;
+
+  constructor(private http: HttpClient) { }
+
+  getAllBaseMs() {
+    return this.http.get(this.url + "base-microservice");
+  }
+}
diff --git a/mod2/ui/src/app/services/breadcrumb.service.ts b/mod2/ui/src/app/services/breadcrumb.service.ts
new file mode 100644 (file)
index 0000000..aa647aa
--- /dev/null
@@ -0,0 +1,73 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { Observable, of, Subject, BehaviorSubject } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class BreadcrumbService {
+
+    breadcrumbs: any[] = [];
+
+    breadcrumbs$: Subject<any[]> = new BehaviorSubject([]);
+    
+    constructor() {
+        this.setBreadcrumbs('', "reset");
+    }
+
+    setBreadcrumbs(component: string, action: string) {
+        if (action == "reset") {
+            this.breadcrumbs = [];
+            this.breadcrumbs.push({ page: 'Home', link: '/home' });
+        }
+
+        //  If the breadcrumb item is clicked, remove evwrything to the right of the clicked item
+        if (action == "crumbClicked") {
+            const pos = this.breadcrumbs.map(function(crumb) { return crumb.page }).indexOf(component);
+            for (1; this.breadcrumbs.length -1 -pos; 1) {
+                this.breadcrumbs.pop()
+            }
+        } else {    // Add the component that was selected
+            if (component == 'Microservices') {
+                this.breadcrumbs.push({ page: 'Microservices', link: '/base-microservices' });
+            } else if (component == 'MS Instances') {
+                this.breadcrumbs.push({ page: 'MS Instances', link: '/ms-instances' });
+            } else if (component == 'Blueprints') {
+                this.breadcrumbs.push({ page: 'Blueprints', link: '/blueprints' });
+            } else if (component == 'Component Specs') {
+                this.breadcrumbs.push({ page: 'Component Specs', link: '/CompSpecs' });
+            } else if (component == 'User Management') {
+                this.breadcrumbs.push({ page: 'User Management', link: '/users' });
+            } else if (component == 'Onboarding Tools') {
+                this.breadcrumbs.push({ page: 'Onboarding Tools', link: '/OnboardingTools' });
+            } else if (component == 'Spec Validator') {
+                this.breadcrumbs.push({ page: 'Spec Validator', link: '/spec-validator' });
+            }
+        }
+        this.notifySubscriber()
+    }
+
+    
+    notifySubscriber() {
+        this.breadcrumbs$.next(this.breadcrumbs);
+    }
+}
+
diff --git a/mod2/ui/src/app/services/comp-spec-add.service.spec.ts b/mod2/ui/src/app/services/comp-spec-add.service.spec.ts
new file mode 100644 (file)
index 0000000..f7f5913
--- /dev/null
@@ -0,0 +1,42 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { CompSpecAddService } from './comp-spec-add.service';
+
+describe('CompSpecAddService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+
+  it('should be created', () => {
+    const service: CompSpecAddService = TestBed.get(CompSpecAddService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/comp-spec-add.service.ts b/mod2/ui/src/app/services/comp-spec-add.service.ts
new file mode 100644 (file)
index 0000000..d269ac4
--- /dev/null
@@ -0,0 +1,45 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { environment } from '../../environments/environment';
+import { Observable } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class CompSpecAddService {
+
+  private URL: string = `http://${environment.api_baseURL}:31001/api/specification/`;
+
+  constructor(private http: HttpClient) { }
+
+  addCsToCatalog(msInstanceId: string, addCsJson: any): Observable<any> {
+    let url = this.URL + msInstanceId;
+    let body = addCsJson;
+    let headers = new HttpHeaders({
+      'Content-Type': 'application/json'
+    });
+
+    let options = {headers:headers}
+    return this.http.post<any>(url, body, options);
+  }
+
+}
diff --git a/mod2/ui/src/app/services/comp-specs-service.service.spec.ts b/mod2/ui/src/app/services/comp-specs-service.service.spec.ts
new file mode 100644 (file)
index 0000000..58cf0a2
--- /dev/null
@@ -0,0 +1,43 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { compSpecsService } from './comp-specs-service.service';
+
+describe('CompSpecsServiceService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+  it('should be created', () => {
+    const service: compSpecsService = TestBed.get(compSpecsService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/comp-specs-service.service.ts b/mod2/ui/src/app/services/comp-specs-service.service.ts
new file mode 100644 (file)
index 0000000..6fcd92c
--- /dev/null
@@ -0,0 +1,43 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { environment } from '../../environments/environment';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class compSpecsService {
+
+  private baseUrl: string = `http://${environment.api_baseURL}:31001/api/specification/`;
+  constructor(private http: HttpClient) {
+  }
+
+  getAllCompSpecs(instanceId) {
+    let url = this.baseUrl + instanceId
+
+    return this.http.get(url);
+  }
+
+  post(msId, body){
+    let url: string = this.baseUrl + msId
+
+    return this.http.post<any>(url, body)
+  }
+}
diff --git a/mod2/ui/src/app/services/deployment-artifact.service.spec.ts b/mod2/ui/src/app/services/deployment-artifact.service.spec.ts
new file mode 100644 (file)
index 0000000..f392732
--- /dev/null
@@ -0,0 +1,43 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { DeploymentArtifactService } from './deployment-artifact.service';
+
+describe('DeploymentArtifactService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+  it('should be created', () => {
+    const service: DeploymentArtifactService = TestBed.get(DeploymentArtifactService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/deployment-artifact.service.ts b/mod2/ui/src/app/services/deployment-artifact.service.ts
new file mode 100644 (file)
index 0000000..e344bff
--- /dev/null
@@ -0,0 +1,71 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { environment } from '../../environments/environment';
+import { AuthService } from './auth.service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DeploymentArtifactService {
+
+  private URL: string = `http://${environment.api_baseURL}:31001/api/deployment-artifact/`;
+  private username = this.authService.getUser().username;
+  private url_User = "?user=" + this.username;
+
+  constructor(private http: HttpClient, private authService: AuthService) { }
+
+  getAllBlueprints() {
+    return this.http.get(this.URL);
+  }
+
+  getStatuses() {
+    let url = this.URL + 'statuses'
+
+    return this.http.get(url)
+  }
+
+  patchBlueprintStatus(update, bpId){
+    let url = this.URL + bpId + "?user=" + this.username
+    let status: string = update
+    let body = {
+      status: status
+    }
+
+    return this.http.patch(url, body)
+  }
+
+  postBlueprint(instanceID: string){
+    let url = ''
+    
+    url = this.URL + instanceID + "?user=" + this.username
+    console.log(url)
+
+    return this.http.post<any>(url, '')
+  }
+
+  deleteBlueprint(instanceID: string){
+    console.log(instanceID)
+    let url = this.URL + instanceID + this.url_User
+
+    return this.http.delete(url)
+  }
+
+}
diff --git a/mod2/ui/src/app/services/download.service.spec.ts b/mod2/ui/src/app/services/download.service.spec.ts
new file mode 100644 (file)
index 0000000..1df7bfd
--- /dev/null
@@ -0,0 +1,41 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { DownloadService } from './download.service';
+
+describe('DownloadService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+  it('should be created', () => {
+    const service: DownloadService = TestBed.get(DownloadService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/download.service.ts b/mod2/ui/src/app/services/download.service.ts
new file mode 100644 (file)
index 0000000..619eff7
--- /dev/null
@@ -0,0 +1,85 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import * as saveAs from 'file-saver';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DownloadService {
+
+  constructor() { }
+
+  /* * * * Download json file * * * */
+  downloadJSON(content, fileName){
+    let file = new Blob([JSON.stringify(content)], { type: 'text;charset=utf-8' });
+    let name: string = `${fileName}.json`
+    saveAs(file, name)
+  }
+
+  /* * * * Export ms instance table to excel or csv * * * */
+  exportTableData(exportTo, downloadElements, arrHeader) {
+    if (exportTo === "excel") {
+      import("xlsx").then(xlsx => {
+        const worksheet = xlsx.utils.json_to_sheet(downloadElements);
+        const workbook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] };
+        const excelBuffer: any = xlsx.write(workbook, { bookType: 'xlsx', type: 'array' });
+        this.saveAsExcelFile(excelBuffer, "Table_Data");
+      });
+    } else if (exportTo === "csv") {
+      let csvData = this.convertToCSV(downloadElements, arrHeader)
+      var blob = new Blob([csvData], { type: 'text/csv' })
+      saveAs(blob, "Table_Data.csv");
+    }
+  }
+  saveAsExcelFile(buffer: any, fileName: string): void {
+    import("file-saver").then(FileSaver => {
+      let EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8';
+      let EXCEL_EXTENSION = '.xlsx';
+      const data: Blob = new Blob([buffer], {
+        type: EXCEL_TYPE
+      });
+      FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION);
+    });
+  }
+  convertToCSV(objArray, headerList) {
+    let array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
+    let str = '';
+    let row = '';
+    for (let index in headerList) {
+      row += headerList[index] + ',';
+    }
+    row = row.slice(0, -1);
+    str += row + '\r\n';
+    for (let i = 0; i < array.length; i++) {
+      let line = '';
+      for (let index in headerList) {
+        let head = headerList[index];
+        if (array[i][head] === null || array[i][head] === undefined) {
+          line += ','
+        } else {
+          if (head === "Labels" && array[i][head].length > 1) { line += '[' + array[i][head].join('] [') + '],'; }
+          else { line += array[i][head] + ','; }
+        }
+      }
+      str += line + '\r\n';
+    }
+    return str;
+  }
+}
diff --git a/mod2/ui/src/app/services/global-filters.service.spec.ts b/mod2/ui/src/app/services/global-filters.service.spec.ts
new file mode 100644 (file)
index 0000000..cadfd3f
--- /dev/null
@@ -0,0 +1,30 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { TestBed } from '@angular/core/testing';
+
+import { GlobalFiltersService } from './global-filters.service';
+
+describe('GlobalFiltersService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: GlobalFiltersService = TestBed.get(GlobalFiltersService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/global-filters.service.ts b/mod2/ui/src/app/services/global-filters.service.ts
new file mode 100644 (file)
index 0000000..0937f92
--- /dev/null
@@ -0,0 +1,55 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class GlobalFiltersService {
+
+  //global filter values
+  instanceName: string;
+  instanceTag: string;
+  release: string;
+  status: string;
+
+  shouldFilter: boolean = false;
+
+  constructor() { }
+
+  setFilters(filters){
+    this.instanceName = filters.instanceName
+    this.instanceTag = filters.instanceTag
+    this.release = filters.release
+    this.status = filters.status
+  }
+
+  getFilters(){
+    let globalFilters = {instanceName: this.instanceName, instanceTag: this.instanceTag, release: this.release, status: this.status}
+    return globalFilters
+  }
+
+  checkShouldFilter(){
+    return this.shouldFilter
+  }
+
+  setShouldFilter(){
+    this.shouldFilter = !this.shouldFilter
+  }
+}
diff --git a/mod2/ui/src/app/services/jwt-interceptor.service.spec.ts b/mod2/ui/src/app/services/jwt-interceptor.service.spec.ts
new file mode 100644 (file)
index 0000000..6fea9ba
--- /dev/null
@@ -0,0 +1,30 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { TestBed } from '@angular/core/testing';
+
+import { JwtInterceptorService } from './jwt-interceptor.service';
+
+describe('JwtInterceptorService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: JwtInterceptorService = TestBed.get(JwtInterceptorService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/jwt-interceptor.service.ts b/mod2/ui/src/app/services/jwt-interceptor.service.ts
new file mode 100644 (file)
index 0000000..2647583
--- /dev/null
@@ -0,0 +1,40 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable, Injector } from '@angular/core';
+import { HttpInterceptor } from '@angular/common/http';
+import { AuthService } from './auth.service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class JwtInterceptorService implements HttpInterceptor{
+
+  constructor(private injector: Injector) { }
+
+  intercept(req: import("@angular/common/http").HttpRequest<any>, next: import("@angular/common/http").HttpHandler): import("rxjs").Observable<import("@angular/common/http").HttpEvent<any>> {
+    let authService = this.injector.get(AuthService);
+    let jwtReq = req.clone({
+      setHeaders: {
+        Authorization: `Bearer ${authService.getJwt()}`
+      }
+    })
+    return next.handle(jwtReq);
+  }
+
+}
diff --git a/mod2/ui/src/app/services/microservice-instance.service.spec.ts b/mod2/ui/src/app/services/microservice-instance.service.spec.ts
new file mode 100644 (file)
index 0000000..b3c4074
--- /dev/null
@@ -0,0 +1,41 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { MicroserviceInstanceService } from './microservice-instance.service';
+
+describe('MicroserviceInstanceService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+  it('should be created', () => {
+    const service: MicroserviceInstanceService = TestBed.get(MicroserviceInstanceService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/microservice-instance.service.ts b/mod2/ui/src/app/services/microservice-instance.service.ts
new file mode 100644 (file)
index 0000000..96cce77
--- /dev/null
@@ -0,0 +1,46 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { environment } from '../../environments/environment';
+import { HttpClient } from '@angular/common/http';
+import { AddMsInstance } from '../microservices/microservices.component';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class MicroserviceInstanceService {
+
+  private url: string = `http://${environment.api_baseURL}:31001/api/microservice-instance/`;
+
+  constructor(private http: HttpClient) { }
+
+  getAllMsInstances() {
+    return this.http.get(this.url);
+  }
+
+  addChangeMsInstance(addOrChange: string, msNameOrId: string, body: AddMsInstance){
+    let URL = this.url + msNameOrId
+
+    if (addOrChange == "ADD") {
+        return this.http.post<any>(URL, body);
+    } else {
+        return this.http.patch<any>(URL, body);
+    }
+  }
+}
diff --git a/mod2/ui/src/app/services/ms-add.service.spec.ts b/mod2/ui/src/app/services/ms-add.service.spec.ts
new file mode 100644 (file)
index 0000000..dc53a56
--- /dev/null
@@ -0,0 +1,42 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { MsAddService } from './ms-add.service';
+
+describe('MsAddService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+
+  it('should be created', () => {
+    const service: MsAddService = TestBed.get(MsAddService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/ms-add.service.ts b/mod2/ui/src/app/services/ms-add.service.ts
new file mode 100644 (file)
index 0000000..367799d
--- /dev/null
@@ -0,0 +1,52 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { environment } from '../../environments/environment';
+import { Observable } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root'
+})
+
+
+
+export class MsAddService {
+
+    private URL: string = `http://${environment.api_baseURL}:31001/api/base-microservice`;
+
+    constructor(private http: HttpClient) { }
+
+    addChangeMsToCatalog(addOrChange, msID, addChangeMsJson): Observable<any> {
+        let url = this.URL;
+        let headers = new HttpHeaders({'Content-Type': 'application/json'});
+        let options = {headers:headers};
+        let body;
+
+        body = addChangeMsJson;
+
+        if (addOrChange == "Add") {
+            return this.http.post<any>(url, body, options);
+        } else {
+            url = url + "/" + msID;
+            return this.http.patch<any>(url, body, options);
+        }
+    }
+
+}
diff --git a/mod2/ui/src/app/services/spec-validation.service.spec.ts b/mod2/ui/src/app/services/spec-validation.service.spec.ts
new file mode 100644 (file)
index 0000000..e83f832
--- /dev/null
@@ -0,0 +1,41 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { SpecValidationService } from './spec-validation.service';
+
+describe('SpecValidationService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule,
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+  it('should be created', () => {
+    const service: SpecValidationService = TestBed.get(SpecValidationService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/spec-validation.service.ts b/mod2/ui/src/app/services/spec-validation.service.ts
new file mode 100644 (file)
index 0000000..2785efe
--- /dev/null
@@ -0,0 +1,53 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { environment } from '../../environments/environment';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class SpecValidationService {
+
+  private URL: string = 'http://zlecdyh2adcc1s2dokr04.1463c9.dyh2a.tci.att.com:31002/';
+
+  constructor(private http: HttpClient) { }
+
+  sendSpecFile(specContent, type, release){
+
+    let url = ''
+
+    if(release === "2007"){
+      url = `${this.URL}v6/api/comp_spec_validator/`
+    }
+
+    let body = {
+      compspec_content: specContent,
+      type: type
+    }
+
+    return this.http.post(url, body)
+  }
+
+  getSchema(type){
+    let url = `${this.URL}v6/api/schema/${type}`
+
+    return this.http.get(url)
+  }
+}
diff --git a/mod2/ui/src/app/services/user.service.spec.ts b/mod2/ui/src/app/services/user.service.spec.ts
new file mode 100644 (file)
index 0000000..979e9fa
--- /dev/null
@@ -0,0 +1,44 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, TestBed } from '@angular/core/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+
+import { UserService } from './user.service';
+
+describe('UserService', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        HttpClientTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: UserService = TestBed.get(UserService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/services/user.service.ts b/mod2/ui/src/app/services/user.service.ts
new file mode 100644 (file)
index 0000000..ec1d503
--- /dev/null
@@ -0,0 +1,53 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { User } from '../models/User';
+import { AuthResponse } from '../models/AuthResponse';
+import { environment } from '../../environments/environment';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class UserService {
+
+  constructor(private http: HttpClient) {}
+
+  getUsers(): Observable<User[]> {
+    return this.http.get<User[]>(`http://${environment.api_baseURL}:31003/api/users/getAll`);
+  }
+
+  editUser(username: string, user: User): Observable<any>{
+    return this.http.patch<any>(`http://${environment.api_baseURL}:31003/api/users/admin/${username}`, user);
+  }
+
+  editProfile(username: string, user: User): Observable<any>{
+    return this.http.patch<any>(`http://${environment.api_baseURL}:31003/api/users/user/${username}`, user);
+  }
+
+  deleteUser(username: string): Observable<{message:string}> {
+    return this.http.delete<{message: string}>(`http://${environment.api_baseURL}:31003/api/users/${username}`);
+  }
+
+  getRoles() {
+    return this.http.get(`http://${environment.api_baseURL}:31003/api/roles`);
+  }
+
+}
diff --git a/mod2/ui/src/app/shared/shared-module.ts b/mod2/ui/src/app/shared/shared-module.ts
new file mode 100644 (file)
index 0000000..7ab21b6
--- /dev/null
@@ -0,0 +1,61 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { NgModule } from '@angular/core';
+import {MatSelectModule} from '@angular/material/select';
+import { FormsModule } from '@angular/forms';
+import {MatTableModule} from '@angular/material/table';
+import { MatFormFieldModule, MatInputModule } from '@angular/material';
+import { MatPaginatorModule } from '@angular/material';
+//import { MatSort } from '@angular/material';
+import { MatToolbarModule, MatIconModule, MatSidenavModule, MatListModule, MatButtonModule } from  '@angular/material';
+
+@NgModule({
+    declarations: [
+    ],
+    imports: [
+        MatSelectModule,
+        FormsModule,
+        MatToolbarModule,
+        MatIconModule,
+        MatSidenavModule,
+        MatListModule,
+        MatButtonModule,
+        MatTableModule,
+        MatPaginatorModule,
+        MatFormFieldModule,
+        MatInputModule
+        //MatSort
+
+    ],
+    exports: [
+        MatSelectModule,
+        FormsModule,
+        MatToolbarModule,
+        MatIconModule,
+        MatSidenavModule,
+        MatListModule,
+        MatButtonModule,
+        MatTableModule,
+        MatPaginatorModule,
+        MatFormFieldModule,
+        MatInputModule
+       // MatSort
+    ]
+    })
+    export class SharedModule { }
\ No newline at end of file
diff --git a/mod2/ui/src/app/user-management/user-management.component.css b/mod2/ui/src/app/user-management/user-management.component.css
new file mode 100644 (file)
index 0000000..495d0f8
--- /dev/null
@@ -0,0 +1,54 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+.pi-trash:hover {
+    color:red;
+}
+.pi-pencil:hover {
+    color:blue;
+}
+
+label {
+    cursor: pointer;
+}
+
+.input{
+    padding-top: 10px;
+}
+.inputLabel { 
+    font-weight: 600;
+    margin-left: 20px;
+    width: 140px;
+}
+
+.inputFieldSm { 
+    width: 200px; 
+    height: 35px; 
+    padding-left: 6px; 
+} 
+.inputFieldMed { 
+    width: 300px; 
+    height: 35px; 
+    padding-left: 6px; 
+} 
+.inputFieldLg { 
+    width: 400px; 
+    height: 35px; 
+    padding-left: 6px; 
+}
\ No newline at end of file
diff --git a/mod2/ui/src/app/user-management/user-management.component.html b/mod2/ui/src/app/user-management/user-management.component.html
new file mode 100644 (file)
index 0000000..6885fe1
--- /dev/null
@@ -0,0 +1,89 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<div style="margin: 0px 20px 10px 20px">
+    <p-table #dt [value]="users" [rowHover]="true">
+        <ng-template pTemplate="caption">
+            <h5><strong>System User List and Management</strong><i style="font-size: smaller;"> ( for admin only )</i></h5>
+            <br/>
+                <a routerLink="/register">Want to register a new user? Click here!</a> 
+        </ng-template>
+        <ng-template pTemplate="header">
+            <tr>
+                <th style="width: 15%">Username (ATT UID)</th>
+                <th>Full Name</th>
+                <th>Roles</th>
+                <!-- <th>Active Status</th> -->
+                <th style="width: 15%">Actions</th>
+            </tr>
+        </ng-template>
+        <ng-template pTemplate="body" let-user>
+            <tr>
+                <td>{{user.username}}</td> 
+                <td>{{user.fullName}}</td>
+                <td>{{user.roles}}</td>
+                <!-- <td>true</td> -->
+                <td>
+                    <i class="pi pi-trash" (click)="handleDelete(user.username)" pTooltip="delete user" tooltipPosition="right"></i>
+                    <i class="pi pi-pencil" (click)="handleEdit(user)" pTooltip="edit user" tooltipPosition="right" style="margin-left: .5em;"></i>
+                </td>
+            </tr>
+        </ng-template>
+    </p-table>
+
+    <!--edit user information dialog-->
+    <p-dialog [(visible)]="editUserFlag" appendTo="body" [modal]="true" [transitionOptions]="'300ms'" [style]="{width: '635px'}" [baseZIndex]="10000"
+    [closable]="true" (onHide)="closeEditDialog()">
+    <p-header style="display: inline-flex;">
+        Edit User Information
+    </p-header>
+
+    <form [formGroup]="editUserForm">
+        <!-- * * * Username * * * -->
+        <div class="input">
+            <label class="inputLabel">ATT UID</label>&nbsp;
+            <b>{{editUser.username}}</b>
+        </div>
+        <!-- * * * User Full Name * * * -->
+        <div class="input">
+            <label class="inputLabel">Full Name</label>&nbsp;
+            <input class="inputFieldSm" type="text" pInputText formControlName="fullName"/>
+        </div>
+        <!-- * * * Roles * * * -->
+        <div class="input">
+            <label class="inputLabel">Roles</label>&nbsp;
+            <p-multiSelect [options]="rolesFromBackend" formControlName="roles" [showToggleAll]="true" [virtualScroll]="true" [filter]="false" [style]="{height:'3.6em', width:'200px'}"></p-multiSelect>  
+            <!-- <b>{{editUser.roles}}</b> -->
+        </div>
+        <!-- * * * Re-Generate Password * * * -->
+        <div class="input">
+            <div class="ui-inputgroup">  
+                <label class="inputLabel">New Password</label>&nbsp;
+                <button pButton type="button" icon="pi pi-refresh" class="ui-button-warn" (click)="generateNewPassword()" ></button>
+                <input type="text" pInputText formControlName="password" placeholder="Generate password" class="ui-inputtext" pTooltip="Password should be greater than 5 characters" tooltipPosition="right"> 
+            </div>
+        </div>
+        <i *ngIf="editUserForm.get('password').errors && editUserForm.get('password').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">password should be at least 6 characters</i>
+        <!-- * * * Submit and Cancel buttons * * * -->
+        <div style="margin-top: 2em; margin-left: 1.3em; margin-bottom: 2em;">
+            <button pButton type="button" (click)="closeEditDialog()" label="Cancel"></button>&nbsp;
+            <button pButton type="submit" (click)="submitEdit(editUser)" class="ui-button-success" label="Submit" style="width: 70px"></button>
+        </div>
+    </form>
+</p-dialog>
+</div>
\ No newline at end of file
diff --git a/mod2/ui/src/app/user-management/user-management.component.spec.ts b/mod2/ui/src/app/user-management/user-management.component.spec.ts
new file mode 100644 (file)
index 0000000..2cb6a6e
--- /dev/null
@@ -0,0 +1,63 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { HttpClientTestingModule } from '@angular/common/http/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
+import { DialogModule } from 'primeng/dialog';
+import { MultiSelectModule } from 'primeng/multiselect';
+import { TableModule } from 'primeng/table';
+
+import { UserManagementComponent } from './user-management.component';
+
+describe('UserManagementComponent', () => {
+  let component: UserManagementComponent;
+  let fixture: ComponentFixture<UserManagementComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [UserManagementComponent],
+      imports: [
+        TableModule,
+        DialogModule,
+        MultiSelectModule,
+        FormsModule,
+        ReactiveFormsModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ],
+      providers: [
+        { provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
+        JwtHelperService
+      ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UserManagementComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/mod2/ui/src/app/user-management/user-management.component.ts b/mod2/ui/src/app/user-management/user-management.component.ts
new file mode 100644 (file)
index 0000000..b7ee6e7
--- /dev/null
@@ -0,0 +1,149 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 { Component, OnInit, ViewChild, ChangeDetectionStrategy } from '@angular/core';
+import { User } from '../models/User';
+import { UserService } from '../services/user.service';
+import { Router } from '@angular/router';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { SelectItem } from 'primeng/api';
+import { AuthService } from '../services/auth.service';
+
+@Component({
+  selector: 'app-user-management',
+  templateUrl: './user-management.component.html',
+  styleUrls: ['./user-management.component.css']
+})
+export class UserManagementComponent implements OnInit {
+
+  users: User[] = [];
+  editUser: User = {
+    username:'',
+    fullName:'',
+    roles: []
+  };
+  editUserFlag: boolean = false;
+  editUserForm: FormGroup;
+  rolesFromBackend = [];
+  selectedRoles : Array<String>= [];
+
+  constructor(private userService: UserService, private router: Router, private fb: FormBuilder, private authService: AuthService) { }
+
+  ngOnInit() {
+      this.userService.getUsers().subscribe((res: User[]) => {
+       this.users = res;
+       this.users.map(user=>{
+         let tempRoles = [];
+         user.roles.map(role=>{
+           tempRoles.push(role.name.substring(5));
+         });
+         user.roles = tempRoles;
+       });
+       
+      });
+      this.editUserForm = this.fb.group({
+        username: '',
+        fullName: '',
+        password: [null, [ Validators.minLength(6)]],
+        roles: [this.selectedRoles, [Validators.required]]
+      });
+      this.userService.getRoles().subscribe(res=>{
+        Object.values(res).forEach(ele=>{
+         this.rolesFromBackend.push({label:ele.substring(5), value:ele});
+        });
+       });
+  }
+
+  handleDelete(username) {
+    const result = window.confirm('Are you sure to delete this user?');
+    if(result === true) {
+      this.userService.deleteUser(username).subscribe(res=>{
+        alert(res.message);
+          this.userService.getUsers().subscribe( r => {
+             this.users = r; 
+             this.users.map(user=>{
+              let tempRoles = [];
+              user.roles.map(role=>{
+                tempRoles.push(role.name.substring(5));
+              });
+              user.roles = tempRoles;
+            });
+      });
+     });
+    } 
+  }
+
+  handleEdit(user) {
+    this.selectedRoles = [];
+    this.editUserFlag = true;
+    this.editUser = user;
+    this.editUserForm.get('username').setValue(user.username);
+    this.editUserForm.get('fullName').setValue(user.fullName);
+    user.roles.map(ele => {
+       let temp = "ROLE_" + ele;
+       this.selectedRoles.push(temp);
+    })
+
+     this.editUserForm.get('roles').setValue(this.selectedRoles);
+
+  }
+
+  closeEditDialog() {
+    this.editUserForm.reset();
+    this.editUserFlag = false;
+  }
+
+  generateNewPassword() {
+    this.editUserForm.value.password = '';
+    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&';
+    var array = new Uint32Array(32);
+    window.crypto.getRandomValues(array);
+    for(let i=0;i<32;i++) {
+      const index = Math.floor(array[i] % chars.length);
+      this.editUserForm.value.password += chars.charAt(index);
+    }
+    this.editUserForm.patchValue({password: this.editUserForm.value.password});
+  }
+
+  submitEdit(user) {
+    this.editUserFlag = false;
+    console.log(this.editUserForm.value.fullName);
+    let tempUser = this.editUserForm.value as User;
+    console.log(tempUser);
+    this.userService.editUser(user.username, this.editUserForm.value as User).subscribe(res=>{
+        alert("User information updated successfully.");
+        this.userService.getUsers().subscribe( r => {
+        this.users = r; 
+        this.users.map(user=>{
+         let tempRoles = [];
+         user.roles.map(role=>{
+           tempRoles.push(role.name.substring(5));
+         });
+         user.roles = tempRoles;
+       });
+       }, (err)=>{
+          // alert(err.error.message);
+          alert("Sorry but your credentials are out of date. Please log in again to resolve this.");
+          this.authService.logout();
+       });   
+    }, (err)=>{
+      alert(err.error.message);
+    });
+  }
+  
+}
diff --git a/mod2/ui/src/assets/.gitkeep b/mod2/ui/src/assets/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/mod2/ui/src/assets/env.js b/mod2/ui/src/assets/env.js
new file mode 100644 (file)
index 0000000..144ee8b
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
\ No newline at end of file
diff --git a/mod2/ui/src/environments/environment.prod.ts b/mod2/ui/src/environments/environment.prod.ts
new file mode 100644 (file)
index 0000000..7edd870
--- /dev/null
@@ -0,0 +1,22 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+export const environment = {
+  production: true,
+  //api_baseURL: process.env.DCAE_HOSTNAME
+};
diff --git a/mod2/ui/src/environments/environment.ts b/mod2/ui/src/environments/environment.ts
new file mode 100644 (file)
index 0000000..1907c5e
--- /dev/null
@@ -0,0 +1,35 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+  production: false,
+  api_baseURL: `${process.env.DCAE_HOSTNAME}`
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.
diff --git a/mod2/ui/src/favicon.ico b/mod2/ui/src/favicon.ico
new file mode 100644 (file)
index 0000000..8081c7c
Binary files /dev/null and b/mod2/ui/src/favicon.ico differ
diff --git a/mod2/ui/src/index.html b/mod2/ui/src/index.html
new file mode 100644 (file)
index 0000000..f2836a8
--- /dev/null
@@ -0,0 +1,36 @@
+<!-- 
+  # ============LICENSE_START=======================================================
+  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ -->
+
+<!doctype html>
+<html lang="en">
+<head>
+  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+  <meta charset="utf-8">
+  <title>ModFe</title>
+  <base href="/">
+  
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
+  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
+  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel=“stylesheet”>
+</head>
+<body>
+  <app-root></app-root>
+</body>
+</html>
diff --git a/mod2/ui/src/main.ts b/mod2/ui/src/main.ts
new file mode 100644 (file)
index 0000000..54bf0d7
--- /dev/null
@@ -0,0 +1,31 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 'hammerjs';
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+  enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+  .catch(err => console.error(err));
diff --git a/mod2/ui/src/polyfills.ts b/mod2/ui/src/polyfills.ts
new file mode 100644 (file)
index 0000000..20fe70e
--- /dev/null
@@ -0,0 +1,81 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ *  with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ *  (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone';  // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/mod2/ui/src/styles.css b/mod2/ui/src/styles.css
new file mode 100644 (file)
index 0000000..6a392de
--- /dev/null
@@ -0,0 +1,33 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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 "~@angular/material/prebuilt-themes/indigo-pink.css";
+@import "~bootstrap/dist/css/bootstrap.css";
+
+html,
+body {
+    height: 100%;
+    overflow: hidden;
+    margin: 0; 
+    padding: 0;
+}
+
+body {
+    margin: 0;
+    font-family: Roboto, "Helvetica Neue", sans-serif;
+}
\ No newline at end of file
diff --git a/mod2/ui/src/test.ts b/mod2/ui/src/test.ts
new file mode 100644 (file)
index 0000000..ccff8e7
--- /dev/null
@@ -0,0 +1,38 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/mod2/ui/tsconfig.app.json b/mod2/ui/tsconfig.app.json
new file mode 100644 (file)
index 0000000..c85ee27
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./out-tsc/app",
+    "types": ["node"]
+  },
+  "include": [
+    "src/**/*.ts"
+  ],
+  "exclude": [
+    "src/test.ts",
+    "src/**/*.spec.ts"
+  ]
+}
diff --git a/mod2/ui/tsconfig.json b/mod2/ui/tsconfig.json
new file mode 100644 (file)
index 0000000..e65fbae
--- /dev/null
@@ -0,0 +1,33 @@
+{
+  "compileOnSave": false,
+  "include": [
+    "typings.d.ts"
+  ],
+  "exclude": [
+    "node_modules/@types/nodes/index.d.ts"
+  ],
+  "compilerOptions": {
+    "baseUrl": "./",
+    "outDir": "./dist/out-tsc",
+    "sourceMap": true,
+    "declaration": false,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "importHelpers": true,
+    "target": "es2015",
+    "types": [
+      "node",
+      "lodash",
+      "typings.d.ts"
+    ],
+    "typeRoots": [
+      "node_modules/@types"
+    ],
+    "lib": [
+      "es2018",
+      "dom"
+    ]
+  }
+}
diff --git a/mod2/ui/tsconfig.spec.json b/mod2/ui/tsconfig.spec.json
new file mode 100644 (file)
index 0000000..6400fde
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./out-tsc/spec",
+    "types": [
+      "jasmine",
+      "node"
+    ]
+  },
+  "files": [
+    "src/test.ts",
+    "src/polyfills.ts"
+  ],
+  "include": [
+    "src/**/*.spec.ts",
+    "src/**/*.d.ts"
+  ]
+}
diff --git a/mod2/ui/tslint.json b/mod2/ui/tslint.json
new file mode 100644 (file)
index 0000000..188bd78
--- /dev/null
@@ -0,0 +1,92 @@
+{
+  "extends": "tslint:recommended",
+  "rules": {
+    "array-type": false,
+    "arrow-parens": false,
+    "deprecation": {
+      "severity": "warn"
+    },
+    "component-class-suffix": true,
+    "contextual-lifecycle": true,
+    "directive-class-suffix": true,
+    "directive-selector": [
+      true,
+      "attribute",
+      "app",
+      "camelCase"
+    ],
+    "component-selector": [
+      true,
+      "element",
+      "app",
+      "kebab-case"
+    ],
+    "import-blacklist": [
+      true,
+      "rxjs/Rx"
+    ],
+    "interface-name": false,
+    "max-classes-per-file": false,
+    "max-line-length": [
+      true,
+      140
+    ],
+    "member-access": false,
+    "member-ordering": [
+      true,
+      {
+        "order": [
+          "static-field",
+          "instance-field",
+          "static-method",
+          "instance-method"
+        ]
+      }
+    ],
+    "no-consecutive-blank-lines": false,
+    "no-console": [
+      true,
+      "debug",
+      "info",
+      "time",
+      "timeEnd",
+      "trace"
+    ],
+    "no-empty": false,
+    "no-inferrable-types": [
+      true,
+      "ignore-params"
+    ],
+    "no-non-null-assertion": true,
+    "no-redundant-jsdoc": true,
+    "no-switch-case-fall-through": true,
+    "no-use-before-declare": true,
+    "no-var-requires": false,
+    "object-literal-key-quotes": [
+      true,
+      "as-needed"
+    ],
+    "object-literal-sort-keys": false,
+    "ordered-imports": false,
+    "quotemark": [
+      true,
+      "single"
+    ],
+    "trailing-comma": false,
+    "no-conflicting-lifecycle": true,
+    "no-host-metadata-property": true,
+    "no-input-rename": true,
+    "no-inputs-metadata-property": true,
+    "no-output-native": true,
+    "no-output-on-prefix": true,
+    "no-output-rename": true,
+    "no-outputs-metadata-property": true,
+    "template-banana-in-box": true,
+    "template-no-negated-async": true,
+    "use-lifecycle-interface": true,
+    "use-pipe-transform-interface": true
+  },
+  "rulesDirectory": [
+    "codelyzer"
+  ]
+}
\ No newline at end of file
diff --git a/mod2/ui/typings.d.ts b/mod2/ui/typings.d.ts
new file mode 100644 (file)
index 0000000..125f045
--- /dev/null
@@ -0,0 +1,32 @@
+/* 
+ *  # ============LICENSE_START=======================================================
+ *  # Copyright (c) 2020 AT&T Intellectual Property. 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=========================================================
+ */
+
+declare var process: Process;
+
+interface GlobalEnvironment {
+    process: Process
+}
+
+interface Process {
+    env: Env
+}
+
+interface Env{
+    DCAE_HOSTNAME: string
+}
+
diff --git a/releases/1.5.1-blueprint-generator.yaml b/releases/1.5.1-blueprint-generator.yaml
new file mode 100644 (file)
index 0000000..ce7fa45
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: 'maven'
+version: '1.5.1'
+project: 'dcaegen2/platform'
+log_dir: 'dcaegen2-platform-mod-bpgenerator-maven-stage-master/229'