added svcapi ui and camunda code 11/1011/1
authorRohan Patel <rp5811@att.com>
Wed, 25 Sep 2019 18:35:28 +0000 (14:35 -0400)
committerRohan Patel <rp5811@att.com>
Wed, 25 Sep 2019 18:38:56 +0000 (14:38 -0400)
Signed-off-by: Rohan Patel <rp5811@att.com>
Change-Id: I197b4b40fe3d047a417479214e471ae26d51fb2b

878 files changed:
otf-camunda/.gitignore [new file with mode: 0644]
otf-camunda/Jenkinsfile [new file with mode: 0644]
otf-camunda/LICENSE.txt [new file with mode: 0644]
otf-camunda/docker/Dockerfile [new file with mode: 0644]
otf-camunda/helm/forceDelete.sh [new file with mode: 0644]
otf-camunda/helm/otf-camunda/Chart.yaml [new file with mode: 0644]
otf-camunda/helm/otf-camunda/mysqlRouterConfig-dev.ini [new file with mode: 0644]
otf-camunda/helm/otf-camunda/mysqlRouterConfig-prod.ini [new file with mode: 0644]
otf-camunda/helm/otf-camunda/mysqlRouterConfig-st.ini [new file with mode: 0644]
otf-camunda/helm/otf-camunda/templates/config.yaml [new file with mode: 0644]
otf-camunda/helm/otf-camunda/templates/deployment.yaml [new file with mode: 0644]
otf-camunda/helm/otf-camunda/templates/secret.yaml [new file with mode: 0644]
otf-camunda/helm/otf-camunda/templates/service.yaml [new file with mode: 0644]
otf-camunda/helm/otf-camunda/values.yaml [new file with mode: 0644]
otf-camunda/mvnw [new file with mode: 0644]
otf-camunda/mvnw.cmd [new file with mode: 0644]
otf-camunda/pom.xml [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/Application.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/cadi/configuration/CadiFilterConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/cadi/configuration/FilterCondition.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/cadi/configuration/OTFApiEnforcementFilterConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/cadi/filter/OTFApiEnforcementFilter.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFAuthorizationConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDataSourceConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDeploymentConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFFailedJobConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJacksonDataConfigurator.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJobConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFLoggingFeature.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OtfCamundaConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/configuration/listener/OTFJobExecutorStartingEventListener.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/CallTestHeadDelegate.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/LogTestResultDelegate.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/PostResultsToDMaaPDelegate.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/RunTestInstanceDelegate.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/SendMailDelegate.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/AsynchronousTestInstanceCallable.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/SynchronousTestInstanceCallable.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/TestHeadCallable.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/exception/TestExecutionException.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowException.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowProcessorException.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/listener/EndEventListener.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/listener/StartEventListener.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/listener/TaskEndEventListener.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/model/ExecutionConstants.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/model/WorkflowResponse.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/plugin/OtfIncidentHandlerPlugin.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/service/CamundaShutdown.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfExternalTaskService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfWorkflowTaskCleanupService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/service/ProcessEngineAwareService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowProcessor.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowRequest.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/ExternalTaskIncidentHandler.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/FailedJobIncidentHandler.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/RsaEncryptDecrypt.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowTask.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowUtility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/Group.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/GroupMember.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/Role.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/TestDefinition.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/TestExecution.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/TestHead.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/TestInstance.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/User.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/DMaaPRequest.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFDeploymentResponse.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFProcessInstanceCompletionResponse.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/PfloNode.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/model/local/UserGroup.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/repository/GroupRepository.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/repository/UserRepository.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/Utility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/database/Generic.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/gson/Convert.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/http/HeadersUtility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/common/utility/sftp/SftpUtility.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/event/TestInstanceCompletionEvent.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/event/TestInstanceIncidentEvent.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/DeleteProcessInstanceService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/DeleteTestDefinitionService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/DeveloperService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/HealthService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/ProcessInstanceCompletionService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/TestControlUnitService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/TestDefinitionDeploymentService.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteProcessInstanceServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteTestDefinitionServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/DeveloperServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/HealthServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/ProcessInstanceCompletionServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/TestControlUnitServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/service/impl/TestDefinitionDeploymentServiceImpl.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/spring/configuration/HttpSecurityConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/spring/configuration/JerseyConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFLoggingFeature.java [new file with mode: 0644]
otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFMongoConfiguration.java [new file with mode: 0644]
otf-camunda/src/main/resources/META-INF/processes.xml.off [new file with mode: 0644]
otf-camunda/src/main/resources/META-INF/securityFilterRules.json [new file with mode: 0644]
otf-camunda/src/main/resources/application.yaml [new file with mode: 0644]
otf-camunda/src/main/resources/banner.txt [new file with mode: 0644]
otf-camunda/src/main/resources/bpmn/pingGoogleDNS.bpmn [new file with mode: 0644]
otf-camunda/src/main/resources/mail-config.properties [new file with mode: 0644]
otf-camunda/src/test/java/org/oran/otf/api/tests/config/DataConfig.java [new file with mode: 0644]
otf-camunda/src/test/java/org/oran/otf/api/tests/config/InMemoryConfig.java [new file with mode: 0644]
otf-camunda/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java [new file with mode: 0644]
otf-camunda/src/test/java/org/oran/otf/api/tests/unit/common/utility/http/HeadersUtilityTest.java [new file with mode: 0644]
otf-camunda/src/test/resources/application-test.properties [new file with mode: 0644]
otf-frontend/.dockerignore [new file with mode: 0644]
otf-frontend/.gitignore [new file with mode: 0644]
otf-frontend/Dockerfile [new file with mode: 0644]
otf-frontend/Jenkinsfile [new file with mode: 0644]
otf-frontend/LICENSES.txt [new file with mode: 0644]
otf-frontend/angular.json [new file with mode: 0644]
otf-frontend/client/.gitignore [new file with mode: 0644]
otf-frontend/client/config/.editorconfig [new file with mode: 0644]
otf-frontend/client/config/.travis.yml [new file with mode: 0644]
otf-frontend/client/config/karma.conf.js [new file with mode: 0644]
otf-frontend/client/config/protractor.conf.js [new file with mode: 0644]
otf-frontend/client/config/tsconfig.json [new file with mode: 0644]
otf-frontend/client/config/tslint.json [new file with mode: 0644]
otf-frontend/client/e2e/app.e2e-spec.ts [new file with mode: 0644]
otf-frontend/client/e2e/app.po.ts [new file with mode: 0644]
otf-frontend/client/e2e/tsconfig.e2e.json [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied.component.html [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/access-denied/access-denied.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/account/account-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/account/account.component.html [new file with mode: 0644]
otf-frontend/client/src/app/account/account.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/account/account.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/account/account.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/account/account.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/account/account.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/app-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/app.component.html [new file with mode: 0644]
otf-frontend/client/src/app/app.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/app.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/app.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/app.global.ts [new file with mode: 0644]
otf-frontend/client/src/app/app.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/app.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/core/core.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/core/core.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/error.interceptor.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/header/header.component.html [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/header/header.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/header/header.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/header/header.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.html [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.html [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot(2).png [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/stats.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/stats.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/control-panel/control-panel.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/dashboard/dashboard.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/feedback/feedback.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout.component.html [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/layout.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/manage-group/manage-group.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/color-picker/ColorPicker.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/color-picker/ColoredRenderer.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/color-picker/index.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/LogTestResultRenderer.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/index.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/cat.gif [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/index.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/LogTestResultPaletteProvider.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/index.js [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/modeler.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/new.bpmn [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/templates/elements.json [new file with mode: 0644]
otf-frontend/client/src/app/layout/modeler/test-definition-element.class.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/onboarding.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/start/start.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/robot-report/robot-report.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/robot-report/robot-report.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/robot-report/robot-report.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/robot-report/robot-report.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/scheduling/scheduling.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/settings/settings.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/tests/tests.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/user-management/user-management.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/login/login-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/login/login.component.html [new file with mode: 0644]
otf-frontend/client/src/app/login/login.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/login/login.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/login/login.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/login/login.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/login/login.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.component.html [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/not-found/not-found.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/router.animations.ts [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error.component.html [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/server-error/server-error.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/factories/bpmn-factory.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/factories/bpmn-factory.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/guard/admin.guard.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/guard/admin.guard.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/guard/auth.guard.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/guard/auth.guard.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/guard/index.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/index.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/base-model.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/bpmn.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/group.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/test-definition.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/test-execution.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/test-head.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/test-instance.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/models/user.model.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/definition-instance.class.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-form/test-definition.class.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/filterNonDeployed.pipe.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/create-test-instance-form/instance.class.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.html [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.html [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/index.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/page-header/page-header.component.html [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/page-header/page-header.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/page-header/page-header.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/page-header/page-header.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/page-header/page-header.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/page-header/page-header.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/stat/stat.component.html [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/stat/stat.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/stat/stat.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/stat/stat.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/stat/stat.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/stat/stat.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/user-select/user-select.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/user-select/user-select.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/user-select/user-select.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/user-select/user-select.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/user-select/user-select.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/user-select/user-select.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.pug [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/pipes/shared-pipes.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/account.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/account.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/auth.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/auth.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/execute.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/feathers.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/feedback.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/feedback.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/file-transfer.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/file-transfer.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/file.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/file.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/group.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/group.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/health.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/health.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/json2html.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/list.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/list.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/model.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/params.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/scheduling.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/scheduling.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-definition.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-definition.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-execution.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-execution.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-head.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-head.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-instance.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/test-instance.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/user.service.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/shared/services/user.service.ts [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup-routing.module.ts [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup.component.html [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup.component.scss [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup.component.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup.component.ts [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup.module.spec.ts [new file with mode: 0644]
otf-frontend/client/src/app/signup/signup.module.ts [new file with mode: 0644]
otf-frontend/client/src/assets/fakedata.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/de.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/en.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/es.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/fa.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/fr.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/it.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/ur.json [new file with mode: 0644]
otf-frontend/client/src/assets/i18n/zh-CHS.json [new file with mode: 0644]
otf-frontend/client/src/assets/images/404image.png [new file with mode: 0644]
otf-frontend/client/src/assets/images/NetworkLogo.jpg [new file with mode: 0644]
otf-frontend/client/src/assets/images/OtfIcon.png [new file with mode: 0644]
otf-frontend/client/src/assets/images/equalizer.gif [new file with mode: 0644]
otf-frontend/client/src/assets/images/logo.png [new file with mode: 0644]
otf-frontend/client/src/assets/images/networkBackground.jpg [new file with mode: 0644]
otf-frontend/client/src/assets/images/networkBackground1.jpg [new file with mode: 0644]
otf-frontend/client/src/assets/images/slider1.jpg [new file with mode: 0644]
otf-frontend/client/src/assets/images/slider2.jpg [new file with mode: 0644]
otf-frontend/client/src/assets/workflows/blank.bpmn [new file with mode: 0644]
otf-frontend/client/src/environments/environment.prod.ts [new file with mode: 0644]
otf-frontend/client/src/environments/environment.ts [new file with mode: 0644]
otf-frontend/client/src/favicon.ico [new file with mode: 0644]
otf-frontend/client/src/global-shims.ts [new file with mode: 0644]
otf-frontend/client/src/index.html [new file with mode: 0644]
otf-frontend/client/src/main.ts [new file with mode: 0644]
otf-frontend/client/src/polyfills.ts [new file with mode: 0644]
otf-frontend/client/src/styles/_responsive.scss [new file with mode: 0644]
otf-frontend/client/src/styles/_rtl.scss [new file with mode: 0644]
otf-frontend/client/src/styles/_spinner.scss [new file with mode: 0644]
otf-frontend/client/src/styles/_utils.scss [new file with mode: 0644]
otf-frontend/client/src/styles/app.scss [new file with mode: 0644]
otf-frontend/client/src/test.ts [new file with mode: 0644]
otf-frontend/client/src/tsconfig.app.json [new file with mode: 0644]
otf-frontend/client/src/tsconfig.json [new file with mode: 0644]
otf-frontend/client/src/tsconfig.spec.json [new file with mode: 0644]
otf-frontend/client/src/typings.d.ts [new file with mode: 0644]
otf-frontend/helm/otf-frontend/Chart.yaml [new file with mode: 0644]
otf-frontend/helm/otf-frontend/templates/deployment.yaml [new file with mode: 0644]
otf-frontend/helm/otf-frontend/templates/secret.yaml [new file with mode: 0644]
otf-frontend/helm/otf-frontend/templates/service.yaml [new file with mode: 0644]
otf-frontend/helm/otf-frontend/values.yaml [new file with mode: 0644]
otf-frontend/package.json [new file with mode: 0644]
otf-frontend/server/config/.eslintrc [new file with mode: 0644]
otf-frontend/server/config/cert/info.txt [new file with mode: 0644]
otf-frontend/server/config/custom-environment-variables.json [new file with mode: 0644]
otf-frontend/server/config/production.json [new file with mode: 0644]
otf-frontend/server/src/agenda/agenda.js [new file with mode: 0644]
otf-frontend/server/src/agenda/controllers/test-execution-controller.js [new file with mode: 0644]
otf-frontend/server/src/agenda/jobs/test-execution-job.js [new file with mode: 0644]
otf-frontend/server/src/agenda/models/test-schedule.js [new file with mode: 0644]
otf-frontend/server/src/agenda/result-emitter.js [new file with mode: 0644]
otf-frontend/server/src/app.js [new file with mode: 0644]
otf-frontend/server/src/feathers/app.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/authentication.js [new file with mode: 0644]
otf-frontend/server/src/feathers/channels.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/agendaJobPopulate.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/checkLocks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/checkPermissions.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/convertToJSON.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/convertToYAML.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/convertToYAMLRecursive.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/createdBy.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/delete-definition.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/delete-version.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/filters.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/log.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/paginate-option.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/permissions/abilities.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/permissions/get-permissions.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/permissions/permissions.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/testDefinitionIsDeployed.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/throw.js [new file with mode: 0644]
otf-frontend/server/src/feathers/hooks/updatedBy.js [new file with mode: 0644]
otf-frontend/server/src/feathers/index.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/file.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/groups.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/jobs.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/test-definitions.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/test-executions.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/test-heads.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/test-instances.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/models/users.model.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/auth-management/auth-management.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/auth-management/auth-management.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/auth-management/notifier.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/bpmn-validate/bpmn.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/execute/execute.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/execute/execute.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/execute/execute.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/feedback/feedback.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/feedback/feedback.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/feedback/feedback.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/file-transfer/file-transfer.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/file-transfer/file-transfer.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/file-transfer/file-transfer.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/files/files.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/files/files.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/groups/groups.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/groups/groups.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/health/health.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/health/health.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/health/health.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/index.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/jobs/jobs.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/jobs/jobs.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/jobs/jobs.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/mailer/mailer.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/mailer/mailer.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/mailer/mailer.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/messages/messages.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/messages/messages.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/messages/messages.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-definitions/test-definitions.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-definitions/test-definitions.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.class.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-executions/test-executions.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-executions/test-executions.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-heads/test-heads.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-heads/test-heads.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-instances/test-instances.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/test-instances/test-instances.service.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/users/users.hooks.js [new file with mode: 0644]
otf-frontend/server/src/feathers/services/users/users.service.js [new file with mode: 0644]
otf-frontend/server/src/lib/logger.js [new file with mode: 0644]
otf-frontend/server/src/lib/mongoose.js [new file with mode: 0644]
otf-frontend/server/src/lib/music.js [new file with mode: 0644]
otf-frontend/server/src/lib/otf-util.js [new file with mode: 0644]
otf-frontend/server/test/app.test.js [new file with mode: 0644]
otf-frontend/server/test/hooks/group-filter.test.js [new file with mode: 0644]
otf-frontend/server/test/services/bpmn-upload.test.js [new file with mode: 0644]
otf-frontend/server/test/services/bpmn-validate.test.js [new file with mode: 0644]
otf-frontend/server/test/services/groups-m.test.js [new file with mode: 0644]
otf-frontend/server/test/services/groups.test.js [new file with mode: 0644]
otf-frontend/server/test/services/health.test.js [new file with mode: 0644]
otf-frontend/server/test/services/strategy-upload.test.js [new file with mode: 0644]
otf-frontend/server/test/services/test-definition.test.js [new file with mode: 0644]
otf-frontend/server/test/services/test-heads.test.js [new file with mode: 0644]
otf-frontend/server/test/services/test-instances.test.js [new file with mode: 0644]
otf-frontend/server/test/services/test-requests.test.js [new file with mode: 0644]
otf-frontend/server/test/services/test-strategies.test.js [new file with mode: 0644]
otf-frontend/server/test/services/tests.test.js [new file with mode: 0644]
otf-frontend/server/test/services/users.test.js [new file with mode: 0644]
otf-ping-test-head/Dockerfile
otf-robot-test-head/Dockerfile
otf-service-api/.gitignore [new file with mode: 0644]
otf-service-api/Jenkinsfile [new file with mode: 0644]
otf-service-api/LICENSE.txt [new file with mode: 0644]
otf-service-api/README.txt [new file with mode: 0644]
otf-service-api/docker/Dockerfile [new file with mode: 0644]
otf-service-api/helm/otf-service-api/Chart.yaml [new file with mode: 0644]
otf-service-api/helm/otf-service-api/templates/deployment.yaml [new file with mode: 0644]
otf-service-api/helm/otf-service-api/templates/secret.yaml [new file with mode: 0644]
otf-service-api/helm/otf-service-api/templates/service.yaml [new file with mode: 0644]
otf-service-api/helm/otf-service-api/values.yaml [new file with mode: 0644]
otf-service-api/pom.xml [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/Application.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/Utilities.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/CadiFilterConfiguration.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/CombinedResourceProvider.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/DataConfig.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/HttpSecurityConfiguration.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/JerseyConfiguration.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilter.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilterConfiguration.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/config/OTFLoggingFeature.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/exception/TestHeadNotFoundException.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/exception/TestParametersException.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/exception/UserNotFoundException.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessDeploymentHandler.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessExecutionHandler.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/HealthService.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/OtfOpenService.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/TestExecutionService.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/TestInstanceService.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/TestStrategyService.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/VirtualTestHeadService.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/impl/HealthServiceImpl.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/impl/OtfOpenServiceImpl.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestExecutionServiceImpl.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestInstanceServiceImpl.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestStrategyServiceImpl.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/api/service/impl/VirtualTestHeadServiceImpl.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/Group.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/GroupMember.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/Role.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/TestDefinition.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/TestExecution.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/TestHead.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/TestInstance.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/User.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/ApiRequest.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/DeployTestStrategyRequest.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/PfloNode.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/TestInstanceCreateRequest.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/UserGroup.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/model/local/WorkflowRequest.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/repository/GroupRepository.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/repository/UserRepository.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/util/HttpUtils.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/RSAEncryptDecrypt.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/Utility.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/database/Generic.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/gson/Convert.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/gson/GsonUtils.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java [new file with mode: 0644]
otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java [new file with mode: 0644]
otf-service-api/src/main/resources/application.properties [new file with mode: 0644]
otf-service-api/src/main/resources/banner.txt [new file with mode: 0644]
otf-service-api/src/main/resources/truststore2018.jks [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/config/DataConfig2.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/config/InMemory.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/ExecutionServiceRouteIT.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/HealthRouteIT.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/InstanceServiceRouteIT.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/OtfOpenRouteIT.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/Permissions/PermissionServiceIT.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/StrategyServiceRouteIT.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/DefinitionTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/ExecutionTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/GroupTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/HeadTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/InstanceTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/UserTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/BpmnTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/DeployTestStrategyRequestTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadNodeTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadResultTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/InstanceCreateRequestTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/OtfApiResponseTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/ParallelFlowInputTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/PfloNodeTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/UserGroupTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/WorkFlowRequestTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/BuildResponseTest.java [new file with mode: 0644]
otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/UserPermissionTest.java [new file with mode: 0644]
otf-service-api/src/test/resources/application-test.properties [new file with mode: 0644]
otf-service-api/swagger.json [new file with mode: 0644]
otf-service-api/swagger.yml [new file with mode: 0644]
otf-ssh-test-head/Dockerfile

diff --git a/otf-camunda/.gitignore b/otf-camunda/.gitignore
new file mode 100644 (file)
index 0000000..7f70bd4
--- /dev/null
@@ -0,0 +1,34 @@
+/target/\r
+tokens/\r
+out/\r
+/otf/\r
+\r
+/src/main/resources/bpmn/local/\r
+/src/main/resources/local/\r
+src/main/resources/otf_dev.p12\r
+\r
+!.mvn/wrapper/maven-wrapper.jar\r
+\r
+### STS ###\r
+.apt_generated\r
+.classpath\r
+.factorypath\r
+.project\r
+.settings\r
+.springBeans\r
+.sts4-cache\r
+\r
+### IntelliJ IDEA ###\r
+.idea\r
+original.idea\r
+*.iws\r
+*.iml\r
+*.ipr\r
+\r
+### NetBeans ###\r
+/nbproject/private/\r
+/build/\r
+/nbbuild/\r
+/dist/\r
+/nbdist/\r
+/.nb-gradle/\r
diff --git a/otf-camunda/Jenkinsfile b/otf-camunda/Jenkinsfile
new file mode 100644 (file)
index 0000000..1548993
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/env groovy\r
+\r
+\r
+properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "username"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_CAMUNDA_DB', defaultValue: "otf_camunda_dev_db"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org.oran.otf"]\r
+\r
+]]])\r
+\r
+\r
+echo "Build branch: ${env.BRANCH_NAME}"\r
+\r
+node("docker") {\r
+    stage 'Checkout'\r
+    checkout scm\r
+    PHASES = PHASE.tokenize('_');\r
+    echo "PHASES : " + PHASES\r
+    pom = readMavenPom file: 'pom.xml'\r
+    ARTIFACT_ID = pom.artifactId;\r
+    VERSION = pom.version;\r
+    LABEL_VERSION = pom.version.replaceAll("\\.", "-");\r
+    echo "LabelVerion: " + LABEL_VERSION\r
+    NAMESPACE = pom.groupId\r
+    echo "Tiller Namespace: " + TILLER_NAMESPACE\r
+    DOCKER_REGISTRY = pom.properties['docker.registry']\r
+\r
+       if( ENV.equalsIgnoreCase("dev") ){\r
+           IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE  + "/" + ARTIFACT_ID + ":" + VERSION\r
+       }\r
+       if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr") ){\r
+           IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID + ":" + VERSION\r
+       }\r
+\r
+    if( ENV.equalsIgnoreCase("st") ){\r
+        IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID + ":" + VERSION\r
+    }\r
+    echo "Artifact: " + IMAGE_NAME\r
+\r
+\r
+       if( ENV.equalsIgnoreCase("dev") ){\r
+               ROUTER_CONFIG="mysqlRouterConfig-dev.ini"\r
+       }\r
+    if( ENV.equalsIgnoreCase("st") ){\r
+        ROUTER_CONFIG="mysqlRouterConfig-st.ini"\r
+    }\r
+       if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){\r
+               ROUTER_CONFIG="mysqlRouterConfig-prod.ini"\r
+       }\r
+\r
+    withEnv(["PATH=${env.PATH}:${tool 'mvn352'}/bin:${tool 'jdk180'}/bin:${env.WORKSPACE}/linux-amd64", "JAVA_HOME=${tool 'jdk180'}", "MAVEN_HOME=${tool 'mvn352'}", "HELM_HOME=${env.WORKSPACE}"]) {\r
+\r
+        echo "JAVA_HOME=${env.JAVA_HOME}"\r
+        echo "MAVEN_HOME=${env.MAVEN_HOME}"\r
+        echo "PATH=${env.PATH}"\r
+        echo "HELM_HOME=${env.HELM_HOME}"\r
+\r
+        wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [\r
+                [fileId: 'maven-settings.xml', variable: 'MAVEN_SETTINGS'],\r
+                [fileId: 'maven-settings-security.xml', variable: 'MAVEN_SETTINGS_SECURITY']\r
+        ]]) {\r
+\r
+\r
+            if (PHASES.contains("BUILD")) {\r
+                stage 'Compile'\r
+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY clean compile'\r
+\r
+                stage 'Unit Test'\r
+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY test'\r
+\r
+                stage 'Package'\r
+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY package'\r
+                //sh 'mvn -DskipTests -Dmaven.test.skip=true -s $MAVEN_SETTINGS package'\r
+\r
+                stage 'Verify'\r
+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY verify'\r
+\r
+                stage 'Publish Artifact'\r
+\r
+                withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {\r
+\r
+                    echo "Artifact: " + DOCKER_REGISTRY\r
+\r
+                    sh """\r
+                                               docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD\r
+                                               docker build -t $IMAGE_NAME -f target/Dockerfile target\r
+                                               docker push $IMAGE_NAME\r
+                                       """\r
+                }\r
+\r
+            }\r
+            if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY") || PHASES.contains("UNDEPLOYFORCE")) {\r
+\r
+                stage 'Init Helm'\r
+\r
+                //check if helm exists if not install\r
+                if (fileExists('linux-amd64/helm')) {\r
+                    sh """\r
+                                               echo "helm is already installed"\r
+                                       """\r
+                } else {\r
+                    //download helm\r
+                    sh """\r
+                                               echo "installing helm"\r
+                                               wget  https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz\r
+                                               tar -xf helm-v2.8.2-linux-amd64.tar.gz\r
+                                               rm helm-v2.8.2-linux-amd64.tar.gz\r
+                                       """\r
+                }\r
+\r
+                withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) {\r
+\r
+                    dir('helm') {\r
+                        //check if charts are valid, and then perform dry run, if successful then upgrade/install charts\r
+\r
+                        if (PHASES.contains("UNDEPLOY")) {\r
+                            stage 'Undeploy'\r
+\r
+                            sh """\r
+                                                       helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID\r
+                                               """\r
+                        }\r
+                        if (PHASES.contains("UNDEPLOYFORCE")) {\r
+                            stage 'Undeploy Force'\r
+\r
+                            sh """\r
+                                \r
+                                                       helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID &\r
+                                chmod 755 forceDelete.sh\r
+                                ./forceDelete.sh $ARTIFACT_ID\r
+                                               """\r
+\r
+                        }\r
+\r
+                        //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace\r
+                        if (PHASES.contains("DEPLOY")) {\r
+                            stage 'Deploy'\r
+                            withCredentials([\r
+                                       usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME_MONGO', passwordVariable: 'PASSWORD_MONGO'),\r
+                                       usernamePassword(credentialsId: OTF_CAMUNDA_DB, usernameVariable: 'USERNAME_CAMUNDA', passwordVariable: 'PASSWORD_CAMUNDA')\r
+                               ]) {\r
+\r
+                                sh """                                                                         \r
+                                                                               \r
+                                                                               echo "Validate Yaml"\r
+                                                                               helm lint $ARTIFACT_ID\r
+\r
+                                                                               echo "View Helm Templates"\r
+                                                                               helm template $ARTIFACT_ID \\r
+                                                                                       --set appName=$ARTIFACT_ID \\r
+                                                                                       --set version=$VERSION  \\r
+                                                                                       --set image=$IMAGE_NAME \\r
+                                                                                       --set namespace=$TILLER_NAMESPACE \\r
+                                                                                       --set env=$ENV \\r
+                                                                                       --set otf.mongo.username=$USERNAME_MONGO \\r
+                                                                                       --set otf.mongo.password=$PASSWORD_MONGO \\r
+                                                                                       --set otf.camunda.db.username=$USERNAME_CAMUNDA \\r
+                                                                                       --set otf.camunda.db.password=$PASSWORD_CAMUNDA \\r
+                                                                                       \r
+\r
+                                                                               echo "Perform Dry Run Of Install"\r
+                                                                               helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \\r
+                                                                                       --set version=$VERSION  \\r
+                                                                                       --set image=$IMAGE_NAME \\r
+                                                                                       --set namespace=$TILLER_NAMESPACE \\r
+                                                                                       --set env=$ENV \\r
+                                                                                       --set otf.mongo.username=$USERNAME_MONGO \\r
+                                                                                       --set otf.mongo.password=$PASSWORD_MONGO \\r
+                                                                                       --set otf.camunda.db.username=$USERNAME_CAMUNDA \\r
+                                                                                       --set otf.camunda.db.password=$PASSWORD_CAMUNDA \\r
+       \r
+                                                                                       \r
+                                                                               echo "Helm Install/Upgrade"\r
+                                                                       helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \\r
+                                                                                       --set version=$VERSION  \\r
+                                                                                       --set image=$IMAGE_NAME \\r
+                                                                                       --set namespace=$TILLER_NAMESPACE \\r
+                                                                                       --set env=$ENV \\r
+                                                                                       --set otf.mongo.username=$USERNAME_MONGO \\r
+                                                                                       --set otf.mongo.password=$PASSWORD_MONGO \\r
+                                                                                       --set otf.camunda.db.username=$USERNAME_CAMUNDA \\r
+                                                                                       --set otf.camunda.db.password=$PASSWORD_CAMUNDA \\r
+\r
+                                                                       """\r
+                            }\r
+                        }\r
+\r
+                    }\r
+                }\r
+            }\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-camunda/LICENSE.txt b/otf-camunda/LICENSE.txt
new file mode 100644 (file)
index 0000000..695ac56
--- /dev/null
@@ -0,0 +1,28 @@
+Unless otherwise specified, all software contained herein is licensed\r
+under the Apache License, Version 2.0 (the "Software License");\r
+you may not use this software except in compliance with the Software\r
+License. You may obtain a copy of the Software License at\r
+\r
+http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+Unless required by applicable law or agreed to in writing, software\r
+distributed under the Software License is distributed on an "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+See the Software License for the specific language governing permissions\r
+and limitations under the Software License.\r
+\r
+\r
+\r
+Unless otherwise specified, all documentation contained herein is licensed\r
+under the Creative Commons License, Attribution 4.0 Intl. (the\r
+"Documentation License"); you may not use this documentation except in\r
+compliance with the Documentation License. You may obtain a copy of the\r
+Documentation License at\r
+\r
+https://creativecommons.org/licenses/by/4.0/\r
+\r
+Unless required by applicable law or agreed to in writing, documentation\r
+distributed under the Documentation License is distributed on an "AS IS"\r
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+implied. See the Documentation License for the specific language governing\r
+permissions and limitations under the Documentation License.\r
diff --git a/otf-camunda/docker/Dockerfile b/otf-camunda/docker/Dockerfile
new file mode 100644 (file)
index 0000000..4df636c
--- /dev/null
@@ -0,0 +1,34 @@
+FROM openjdk:8\r
+\r
+ENV ENV=development\r
+ENV NAMESPACE=namespace\r
+ENV APP_NAME=otf-camunda\r
+ENV EXECUTORS_ACTIVE=true\r
+ENV OTF_MONGO_USERNAME=username\r
+ENV OTF_MONGO_PASSWORD=password\r
+ENV OTF_MONGO_HOSTS=localhost:27017\r
+ENV OTF_MONGO_REPLICASET=mongoOTF\r
+ENV OTF_MONGO_DATABASE=otf\r
+ENV OTF_CAMUNDA_DB_URL=localhost:3306/otf-camunda\r
+ENV OTF_CAMUNDA_DB_USERNAME=username\r
+ENV OTF_CAMUNDA_DB_PASSWORD=password\r
+ENV AAF_PERM_TYPE=type\r
+ENV CADI_HOSTNAME=localhost\r
+ENV AAF_ID=username\r
+ENV AAF_MECH_PASSWORD=password\r
+ENV AAF_PASSWORD=password\r
+ENV CADI_KEYFILE=/opt/secret/keyfile\r
+ENV OTF_CERT_PATH=opt/cert/cert.p12\r
+ENV OTF_CERT_PASS=password\r
+ENV APP_VERSION=1.0\r
+ENV PRIVATE_KEY=opt/cert/cert.key\r
+ENV PRIVATE_KEY_USERNAME=username\r
+ENV PRIVATE_KEY_PASSPHRASE=password\r
+\r
+COPY otf-camunda.jar app.jar\r
+\r
+RUN mkdir -p /otf/logs\r
+\r
+ADD src src\r
+\r
+ENTRYPOINT ["java", "-jar", "app.jar"]
\ No newline at end of file
diff --git a/otf-camunda/helm/forceDelete.sh b/otf-camunda/helm/forceDelete.sh
new file mode 100644 (file)
index 0000000..9347939
--- /dev/null
@@ -0,0 +1,11 @@
+#/bin/bash\r
+podName=$1\r
+echo $podName\r
+podInfo=$(kubectl get pods -l app=$1 -o custom-columns=:metadata.name)\r
+echo $podInfo\r
+podArray=(`echo ${podInfo}`)\r
+for var in "${podArray[@]}"\r
+do\r
+  echo "Force deleting pod ${var}"\r
+  kubectl delete pods ${var} --grace-period=0 --force --ignore-not-found=true\r
+done
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/Chart.yaml b/otf-camunda/helm/otf-camunda/Chart.yaml
new file mode 100644 (file)
index 0000000..9c55445
--- /dev/null
@@ -0,0 +1,5 @@
+apiVersion: v1\r
+appVersion: "1.0"\r
+description: A Helm chart the OTF TCU camunda engine\r
+name: otf-camunda\r
+version: 0.0.1-SNAPSHOT
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/mysqlRouterConfig-dev.ini b/otf-camunda/helm/otf-camunda/mysqlRouterConfig-dev.ini
new file mode 100644 (file)
index 0000000..f858b47
--- /dev/null
@@ -0,0 +1,9 @@
+[DEFAULT]\r
+\r
+[logger]\r
+level = INFO\r
+\r
+[routing]\r
+bind_address = 0.0.0.0:3306\r
+destinations = localhost:3306\r
+mode = read-write
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/mysqlRouterConfig-prod.ini b/otf-camunda/helm/otf-camunda/mysqlRouterConfig-prod.ini
new file mode 100644 (file)
index 0000000..6ad94a3
--- /dev/null
@@ -0,0 +1,9 @@
+[DEFAULT]\r
+\r
+[logger]\r
+level = INFO\r
+\r
+[routing]\r
+bind_address = 0.0.0.0:3306\r
+destinations = 135.49.207.141:3316,135.49.207.140:3316,130.6.37.195:3316\r
+mode = read-write
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/mysqlRouterConfig-st.ini b/otf-camunda/helm/otf-camunda/mysqlRouterConfig-st.ini
new file mode 100644 (file)
index 0000000..f858b47
--- /dev/null
@@ -0,0 +1,9 @@
+[DEFAULT]\r
+\r
+[logger]\r
+level = INFO\r
+\r
+[routing]\r
+bind_address = 0.0.0.0:3306\r
+destinations = localhost:3306\r
+mode = read-write
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/templates/config.yaml b/otf-camunda/helm/otf-camunda/templates/config.yaml
new file mode 100644 (file)
index 0000000..8c59908
--- /dev/null
@@ -0,0 +1,13 @@
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: {{ .Values.appName }}-config\r
+data:\r
+  router_config: |+\r
+{{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+{{ .Files.Get .Values.otf.camunda.router.config.prod | indent 4}}\r
+{{ else if eq .Values.env "st"}}\r
+{{ .Files.Get .Values.otf.camunda.router.config.st | indent 4}}\r
+{{ else }}\r
+{{ .Files.Get .Values.otf.camunda.router.config.dev | indent 4}}\r
+{{ end }}
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/templates/deployment.yaml b/otf-camunda/helm/otf-camunda/templates/deployment.yaml
new file mode 100644 (file)
index 0000000..89f751e
--- /dev/null
@@ -0,0 +1,314 @@
+apiVersion: apps/v1beta1\r
+kind: StatefulSet\r
+metadata:\r
+  name: {{ .Values.appName}}-{{ .Values.env }}\r
+  namespace: {{.Values.namespace}}\r
+  labels:\r
+    app: {{ .Values.appName}}\r
+    version: {{.Values.version}}\r
+spec:\r
+  revisionHistoryLimit: 1\r
+  minReadySeconds: 10\r
+  strategy:\r
+  # indicate which strategy we want for rolling update\r
+    type: RollingUpdate\r
+    rollingUpdate:\r
+      maxSurge: 3\r
+      maxUnavailable: 1\r
+  replicas: {{ .Values.replicas}}\r
+  selector:\r
+    matchLabels:\r
+      app: {{ .Values.appName}}\r
+      version: {{.Values.version}}\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: {{ .Values.appName}}\r
+        version: {{.Values.version}}\r
+    spec:\r
+      serviceAccount: default\r
+      volumes:\r
+      - name: {{ .Values.appName}}-aaf-volume\r
+        secret:\r
+          secretName: {{.Values.sharedSecret}}\r
+      - name: {{ .Values.appName}}-keyfile-volume\r
+        secret:\r
+          secretName: {{.Values.sharedSecret}}\r
+          optional: true\r
+          items:\r
+          - key: cadi_keyfile\r
+            path: keyfile\r
+      - name: {{ .Values.appName}}-cert-volume\r
+        secret:\r
+          secretName: {{.Values.sharedCert}}\r
+          optional: true\r
+          items:\r
+          - key: PKCS12_CERT\r
+            {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+            path: {{ .Values.cert.prod.name | quote }}\r
+            {{ else if eq  .Values.env "st" }}\r
+            path: {{ .Values.cert.st.name | quote }}\r
+            {{ else }}\r
+            path: {{ .Values.cert.dev.name | quote }}\r
+            {{ end }}\r
+          - key: private_key\r
+            path: {{ .Values.Secret.privateKey.name }}\r
+      - name: {{.Values.appName}}-config-volume\r
+        configMap:\r
+          name: {{.Values.appName}}-config\r
+          items:\r
+          - key: router_config\r
+            path: config.ini\r
+      {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}\r
+      {{else}}\r
+      - name: logging-pvc\r
+        persistentVolumeClaim:\r
+          {{if eq .Values.env "prod"}}\r
+          claimName: {{ .Values.pvc.prod | quote }}\r
+          {{ else }}\r
+          claimName: {{ .Values.pvc.dev | quote }}\r
+          {{ end }}\r
+      {{end}}\r
+      containers:\r
+      - name: mysql-router\r
+        image: {{ .Values.otf.camunda.router.image }}\r
+        imagePullPolicy: Always\r
+        ports:\r
+        - name: http\r
+          containerPort: {{ .Values.otf.camunda.router.port }}\r
+          protocol: TCP\r
+        {{ if eq .Values.env "st"}}\r
+        resources:\r
+          limits:\r
+            memory: "1Gi"\r
+            cpu: "500m"\r
+          requests:\r
+            memory: "512Mi"\r
+            cpu: "100m"\r
+        {{else}}\r
+        resources:\r
+          limits:\r
+            memory: "4Gi"\r
+            cpu: "2"\r
+          requests:\r
+            memory: "2Gi"\r
+            cpu: "1"\r
+        {{end}}\r
+        args: ["--config=/opt/config/config.ini"]\r
+        lifecycle:\r
+          preStop:\r
+            exec:\r
+              command: ["/bin/sh", "-c", {{ "sleep 0" | replace "0" (.Values.terminationGracePeriodSeconds | toString) | quote}} ]\r
+        volumeMounts:\r
+        - name: {{.Values.appName}}-config-volume\r
+          mountPath: /opt/config\r
+      - name: {{ .Values.appName}}\r
+        image: {{ .Values.image}}\r
+        imagePullPolicy: Always\r
+        ports:\r
+        - name: http\r
+          containerPort: {{ .Values.otf.camunda.tcu.port }}\r
+          nodePort: {{.Values.nodePort}}\r
+          protocol: TCP\r
+        {{ if eq .Values.env "st"}}\r
+        resources:\r
+          limits:\r
+            memory: "6Gi"\r
+            cpu: "2.8"\r
+          requests:\r
+            memory: "2Gi"\r
+            cpu: "1.5"\r
+        {{else}}\r
+        resources:\r
+          limits:\r
+            memory: "10Gi"\r
+            cpu: "6"\r
+          requests:\r
+            memory: "4Gi"\r
+            cpu: "2"\r
+        {{end}}\r
+        env:\r
+        - name: ENV\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: "production"\r
+          {{ else if eq .Values.env "st" }}\r
+          value: "system_test"\r
+          {{ else }}\r
+          value: "development"\r
+          {{ end }}\r
+        - name: NAMESPACE\r
+          value: {{.Values.namespace}}\r
+        - name: APP_NAME\r
+          value: {{ .Values.appName}}\r
+        - name: EXECUTORS_ACTIVE\r
+          {{if eq .Values.env "prod"}}\r
+          value: {{ .Values.otf.camunda.executors_active.prod | quote }}\r
+          {{else if eq .Values.env "prod-dr"}}\r
+          value: {{ .Values.otf.camunda.executors_active.prod_dr | quote }}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.otf.camunda.executors_active.st | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.camunda.executors_active.dev | quote }}\r
+          {{ end }}\r
+        - name: OTF_MONGO_USERNAME\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: mongo_username\r
+              optional: true\r
+        - name: OTF_MONGO_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: mongo_password\r
+              optional: true\r
+        - name: OTF_MONGO_HOSTS\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.mongo.prod.host | quote }}\r
+          {{ else if eq  .Values.env "st" }}\r
+          value: {{ .Values.otf.mongo.st.host | quote }}\r
+          {{ else }}\r
+          value: {{.Values.otf.mongo.dev.host | quote }}\r
+          {{ end }}\r
+        - name: OTF_MONGO_REPLICASET\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.mongo.prod.replicaSet | quote }}\r
+          {{ else if eq .Values.env "st"}}\r
+          value: {{ .Values.otf.mongo.st.replicaSet | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.mongo.dev.replicaSet | quote }}\r
+          {{ end }}\r
+        - name: OTF_MONGO_DATABASE\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.mongo.prod.database | quote }}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.otf.mongo.st.database | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.mongo.dev.database | quote }}\r
+          {{ end }}\r
+        - name: OTF_CAMUNDA_DB_URL\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.camunda.db.prod.url}}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.otf.camunda.db.st.url}}\r
+          {{ else }}\r
+          value: {{ .Values.otf.camunda.db.dev.url}}\r
+          {{ end }}\r
+        - name: OTF_CAMUNDA_DB_USERNAME\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: camunda_db_username\r
+              optional: true\r
+        - name: OTF_CAMUNDA_DB_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: camunda_db_password\r
+              optional: true\r
+        - name: AAF_PERM_TYPE\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.aafPermType.prod | quote }}\r
+          {{ else if  eq .Values.env "st"}}\r
+          value: {{ .Values.aafPermType.st | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.aafPermType.dev | quote }}\r
+          {{ end }}\r
+        - name: CADI_HOSTNAME\r
+          {{if eq .Values.env "prod"}}\r
+          value: {{ .Values.cadiHostname.prod | quote }}\r
+          {{else if eq .Values.env "prod-dr"}}\r
+          value: {{ .Values.cadiHostname.prod_dr | quote }}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.cadiHostname.st | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.cadiHostname.dev | quote }}\r
+          {{ end }}\r
+        - name: AAF_ID\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_id\r
+              optional: true\r
+        - name: AAF_MECH_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_mech_password\r
+              optional: true\r
+        - name: AAF_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_password\r
+              optional: true\r
+        - name: CADI_KEYFILE\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: keyfile_secret_path\r
+              optional: true\r
+        - name: OTF_CERT_PATH\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.cert.prod.path | quote }}\r
+          {{ else if eq  .Values.env "st" }}\r
+          value: {{ .Values.cert.st.path | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.cert.dev.path | quote }}\r
+          {{ end }}\r
+        - name: OTF_CERT_PASS\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedCert}}\r
+              key: PKCS12_KEY\r
+              optional: true\r
+        - name: APP_VERSION\r
+          value: {{.Values.version}}\r
+        - name: PRIVATE_KEY\r
+          value: {{ .Values.Secret.privateKey.path }}\r
+        - name: PRIVATE_KEY_USERNAME\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{.Values.sharedCert}}\r
+              key: private_key_username\r
+              optional: true\r
+        - name: PRIVATE_KEY_PASSPHRASE\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{.Values.sharedCert}}\r
+              key: private_key_passphrase\r
+              optional: true\r
+        volumeMounts:\r
+        - name: {{.Values.appName}}-keyfile-volume\r
+          mountPath: /opt/secret\r
+        - name: {{.Values.appName}}-cert-volume\r
+          mountPath: /opt/cert\r
+        {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}\r
+        {{else}}\r
+        - name: logging-pvc\r
+          mountPath: "/otf/logs"\r
+        {{end}}   \r
+        livenessProbe:\r
+          httpGet:\r
+            path: /otf/health/v1\r
+            port: http\r
+            scheme: HTTPS\r
+            httpHeaders:\r
+            - name: X-Custom-Header\r
+              value: Alive\r
+          initialDelaySeconds: 30\r
+          timeoutSeconds: 30\r
+          periodSeconds: 30\r
+        readinessProbe:\r
+          httpGet:\r
+            path: /otf/health/v1\r
+            port: http\r
+            scheme: HTTPS\r
+            httpHeaders:\r
+            - name: X-Custom-Header\r
+              value: Ready\r
+          initialDelaySeconds: 30\r
+          timeoutSeconds: 30\r
+          periodSeconds: 30\r
+      restartPolicy: Always\r
+      terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds}}\r
diff --git a/otf-camunda/helm/otf-camunda/templates/secret.yaml b/otf-camunda/helm/otf-camunda/templates/secret.yaml
new file mode 100644 (file)
index 0000000..52438d2
--- /dev/null
@@ -0,0 +1,10 @@
+apiVersion: v1\r
+kind: Secret\r
+metadata:\r
+  name: {{ .Values.appName}}\r
+type: Opaque\r
+data:\r
+  mongo_username: {{ .Values.otf.mongo.username | b64enc}}\r
+  mongo_password: {{ .Values.otf.mongo.password | b64enc}}\r
+  camunda_db_username: {{ .Values.otf.camunda.db.username | b64enc}}\r
+  camunda_db_password: {{ .Values.otf.camunda.db.password | b64enc}}
\ No newline at end of file
diff --git a/otf-camunda/helm/otf-camunda/templates/service.yaml b/otf-camunda/helm/otf-camunda/templates/service.yaml
new file mode 100644 (file)
index 0000000..ae5f832
--- /dev/null
@@ -0,0 +1,18 @@
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: {{ .Values.appName }}\r
+  namespace: {{ .Values.namespace}}\r
+  labels:\r
+    app: {{ .Values.appName }}\r
+    version: {{ .Values.version}}\r
+spec:\r
+  type: NodePort\r
+  ports:\r
+  - name: http\r
+    port: {{ .Values.otf.camunda.tcu.port }}\r
+    protocol: TCP\r
+    nodePort: {{ .Values.nodePort}}\r
+  selector:\r
+    app: {{ .Values.appName }}\r
+    version: {{ .Values.version}}\r
diff --git a/otf-camunda/helm/otf-camunda/values.yaml b/otf-camunda/helm/otf-camunda/values.yaml
new file mode 100644 (file)
index 0000000..cbd86f0
--- /dev/null
@@ -0,0 +1,86 @@
+appName: otf-camunda\r
+version: 0.0.1-SNAPSHOT\r
+image: otf-camunda:0.0.1-SNAPSHOT\r
+namespace: org.oran.otf\r
+nodePort: 31313\r
+replicas: 2\r
+terminationGracePeriodSeconds: 360\r
+env: dev\r
+\r
+# Environment variables for the service api.\r
+otf:\r
+  mongo:\r
+    dev:\r
+      host: localhost:27017,localhost:27017,localhost:27017\r
+      replicaSet: mongoOTF\r
+      database: otf\r
+    st:\r
+      host: localhost:27017,localhost:27017,localhost:27017\r
+      replicaSet: mongoOTF\r
+      database: otf_st\r
+    prod:\r
+      host: localhost:18720,localhost:18720,localhost:18720\r
+      replicaSet: otf-rs-prod2\r
+      database: otf\r
+    username: "test"\r
+    password: "test"\r
+  camunda:\r
+    executors_active:\r
+      dev: true\r
+      st: true\r
+      prod: false\r
+      prod_dr: true\r
+    tcu:\r
+      port: 8443\r
+    db:\r
+      dev:\r
+        url: localhost:3306/otf-camunda\r
+      st:\r
+        url: localhost:3306/otf_st-camunda\r
+      prod:\r
+        url: localhost:3306/otf-camunda\r
+      username: username\r
+      password: password\r
+    router:\r
+      config:\r
+        dev: mysqlRouterConfig-dev.ini\r
+        st: mysqlRouterConfig-st.ini\r
+        prod: mysqlRouterConfig-prod.ini\r
+      image: mysql/mysql-router\r
+      port: 3306\r
+# permission type for aaf\r
+aafPermType:\r
+  dev: org.oran.otf.dev.camunda\r
+  st: org.oran.otf.st.camunda   \r
+  prod: org.oran.otf.prod.camunda\r
+\r
+cadiHostname:\r
+  dev: localhost\r
+  st: localhost\r
+  prod: localhost\r
+  prod_dr: localhost\r
+\r
+  \r
+# Secret related information.\r
+sharedSecret: otf-aaf-credential-generator\r
+sharedCert: otf-cert-secret-builder\r
+cert:\r
+  dev: \r
+    name: otf_dev.p12\r
+    path: opt/cert/otf_dev.p12\r
+  st: \r
+    name: otf_st.p12\r
+    path: opt/cert/otf_st.p12\r
+  prod: \r
+    name: otf_prod.p12\r
+    path: opt/cert/otf_prod.p12\r
+\r
+Secret:\r
+  privateKey:\r
+    name: key.key\r
+    path: opt/cert/key.key\r
+\r
+pvc:\r
+  dev: org-oran-otf-dev-logs-pv\r
+  prod: org-oran-otf-prod-logs-pv\r
+\r
diff --git a/otf-camunda/mvnw b/otf-camunda/mvnw
new file mode 100644 (file)
index 0000000..7778d42
--- /dev/null
@@ -0,0 +1,225 @@
+#!/bin/sh\r
+# ----------------------------------------------------------------------------\r
+# Licensed to the Apache Software Foundation (ASF) under one\r
+# or more contributor license agreements.  See the NOTICE file\r
+# distributed with this work for additional information\r
+# regarding copyright ownership.  The ASF licenses this file\r
+# to you under the Apache License, Version 2.0 (the\r
+# "License"); you may not use this file except in compliance\r
+# with the License.  You may obtain a copy of the License at\r
+#\r
+#    http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing,\r
+# software distributed under the License is distributed on an\r
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r
+# KIND, either express or implied.  See the License for the\r
+# specific language governing permissions and limitations\r
+# under the License.\r
+# ----------------------------------------------------------------------------\r
+\r
+# ----------------------------------------------------------------------------\r
+# Maven2 Start Up Batch script\r
+#\r
+# Required ENV vars:\r
+# ------------------\r
+#   JAVA_HOME - location of a JDK home dir\r
+#\r
+# Optional ENV vars\r
+# -----------------\r
+#   M2_HOME - location of maven2's installed home dir\r
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\r
+#     e.g. to debug Maven itself, use\r
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r
+# ----------------------------------------------------------------------------\r
+\r
+if [ -z "$MAVEN_SKIP_RC" ] ; then\r
+\r
+  if [ -f /etc/mavenrc ] ; then\r
+    . /etc/mavenrc\r
+  fi\r
+\r
+  if [ -f "$HOME/.mavenrc" ] ; then\r
+    . "$HOME/.mavenrc"\r
+  fi\r
+\r
+fi\r
+\r
+# OS specific support.  $var _must_ be set to either true or false.\r
+cygwin=false;\r
+darwin=false;\r
+mingw=false\r
+case "`uname`" in\r
+  CYGWIN*) cygwin=true ;;\r
+  MINGW*) mingw=true;;\r
+  Darwin*) darwin=true\r
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\r
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\r
+    if [ -z "$JAVA_HOME" ]; then\r
+      if [ -x "/usr/libexec/java_home" ]; then\r
+        export JAVA_HOME="`/usr/libexec/java_home`"\r
+      else\r
+        export JAVA_HOME="/Library/Java/Home"\r
+      fi\r
+    fi\r
+    ;;\r
+esac\r
+\r
+if [ -z "$JAVA_HOME" ] ; then\r
+  if [ -r /etc/gentoo-release ] ; then\r
+    JAVA_HOME=`java-config --jre-home`\r
+  fi\r
+fi\r
+\r
+if [ -z "$M2_HOME" ] ; then\r
+  ## resolve links - $0 may be a link to maven's home\r
+  PRG="$0"\r
+\r
+  # need this for relative symlinks\r
+  while [ -h "$PRG" ] ; do\r
+    ls=`ls -ld "$PRG"`\r
+    link=`expr "$ls" : '.*-> \(.*\)$'`\r
+    if expr "$link" : '/.*' > /dev/null; then\r
+      PRG="$link"\r
+    else\r
+      PRG="`dirname "$PRG"`/$link"\r
+    fi\r
+  done\r
+\r
+  saveddir=`pwd`\r
+\r
+  M2_HOME=`dirname "$PRG"`/..\r
+\r
+  # make it fully qualified\r
+  M2_HOME=`cd "$M2_HOME" && pwd`\r
+\r
+  cd "$saveddir"\r
+  # echo Using m2 at $M2_HOME\r
+fi\r
+\r
+# For Cygwin, ensure paths are in UNIX format before anything is touched\r
+if $cygwin ; then\r
+  [ -n "$M2_HOME" ] &&\r
+    M2_HOME=`cygpath --unix "$M2_HOME"`\r
+  [ -n "$JAVA_HOME" ] &&\r
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`\r
+  [ -n "$CLASSPATH" ] &&\r
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`\r
+fi\r
+\r
+# For Migwn, ensure paths are in UNIX format before anything is touched\r
+if $mingw ; then\r
+  [ -n "$M2_HOME" ] &&\r
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"\r
+  [ -n "$JAVA_HOME" ] &&\r
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"\r
+  # TODO classpath?\r
+fi\r
+\r
+if [ -z "$JAVA_HOME" ]; then\r
+  javaExecutable="`which javac`"\r
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then\r
+    # readlink(1) is not available as standard on Solaris 10.\r
+    readLink=`which readlink`\r
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then\r
+      if $darwin ; then\r
+        javaHome="`dirname \"$javaExecutable\"`"\r
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"\r
+      else\r
+        javaExecutable="`readlink -f \"$javaExecutable\"`"\r
+      fi\r
+      javaHome="`dirname \"$javaExecutable\"`"\r
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`\r
+      JAVA_HOME="$javaHome"\r
+      export JAVA_HOME\r
+    fi\r
+  fi\r
+fi\r
+\r
+if [ -z "$JAVACMD" ] ; then\r
+  if [ -n "$JAVA_HOME"  ] ; then\r
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then\r
+      # IBM's JDK on AIX uses strange locations for the executables\r
+      JAVACMD="$JAVA_HOME/jre/sh/java"\r
+    else\r
+      JAVACMD="$JAVA_HOME/bin/java"\r
+    fi\r
+  else\r
+    JAVACMD="`which java`"\r
+  fi\r
+fi\r
+\r
+if [ ! -x "$JAVACMD" ] ; then\r
+  echo "Error: JAVA_HOME is not defined correctly." >&2\r
+  echo "  We cannot execute $JAVACMD" >&2\r
+  exit 1\r
+fi\r
+\r
+if [ -z "$JAVA_HOME" ] ; then\r
+  echo "Warning: JAVA_HOME environment variable is not set."\r
+fi\r
+\r
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\r
+\r
+# traverses directory structure from process work directory to filesystem root\r
+# first directory with .mvn subdirectory is considered project base directory\r
+find_maven_basedir() {\r
+\r
+  if [ -z "$1" ]\r
+  then\r
+    echo "Path not specified to find_maven_basedir"\r
+    return 1\r
+  fi\r
+\r
+  basedir="$1"\r
+  wdir="$1"\r
+  while [ "$wdir" != '/' ] ; do\r
+    if [ -d "$wdir"/.mvn ] ; then\r
+      basedir=$wdir\r
+      break\r
+    fi\r
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\r
+    if [ -d "${wdir}" ]; then\r
+      wdir=`cd "$wdir/.."; pwd`\r
+    fi\r
+    # end of workaround\r
+  done\r
+  echo "${basedir}"\r
+}\r
+\r
+# concatenates all lines of a file\r
+concat_lines() {\r
+  if [ -f "$1" ]; then\r
+    echo "$(tr -s '\n' ' ' < "$1")"\r
+  fi\r
+}\r
+\r
+BASE_DIR=`find_maven_basedir "$(pwd)"`\r
+if [ -z "$BASE_DIR" ]; then\r
+  exit 1;\r
+fi\r
+\r
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}\r
+echo $MAVEN_PROJECTBASEDIR\r
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"\r
+\r
+# For Cygwin, switch paths to Windows format before running java\r
+if $cygwin; then\r
+  [ -n "$M2_HOME" ] &&\r
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`\r
+  [ -n "$JAVA_HOME" ] &&\r
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`\r
+  [ -n "$CLASSPATH" ] &&\r
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`\r
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&\r
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`\r
+fi\r
+\r
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r
+\r
+exec "$JAVACMD" \\r
+  $MAVEN_OPTS \\r
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \\r
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \\r
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"\r
diff --git a/otf-camunda/mvnw.cmd b/otf-camunda/mvnw.cmd
new file mode 100644 (file)
index 0000000..48c810e
--- /dev/null
@@ -0,0 +1,143 @@
+@REM ----------------------------------------------------------------------------\r
+@REM Licensed to the Apache Software Foundation (ASF) under one\r
+@REM or more contributor license agreements.  See the NOTICE file\r
+@REM distributed with this work for additional information\r
+@REM regarding copyright ownership.  The ASF licenses this file\r
+@REM to you under the Apache License, Version 2.0 (the\r
+@REM "License"); you may not use this file except in compliance\r
+@REM with the License.  You may obtain a copy of the License at\r
+@REM\r
+@REM    http://www.apache.org/licenses/LICENSE-2.0\r
+@REM\r
+@REM Unless required by applicable law or agreed to in writing,\r
+@REM software distributed under the License is distributed on an\r
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r
+@REM KIND, either express or implied.  See the License for the\r
+@REM specific language governing permissions and limitations\r
+@REM under the License.\r
+@REM ----------------------------------------------------------------------------\r
+\r
+@REM ----------------------------------------------------------------------------\r
+@REM Maven2 Start Up Batch script\r
+@REM\r
+@REM Required ENV vars:\r
+@REM JAVA_HOME - location of a JDK home dir\r
+@REM\r
+@REM Optional ENV vars\r
+@REM M2_HOME - location of maven2's installed home dir\r
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\r
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r
+@REM     e.g. to debug Maven itself, use\r
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r
+@REM ----------------------------------------------------------------------------\r
+\r
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r
+@echo off\r
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\r
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%\r
+\r
+@REM set %HOME% to equivalent of $HOME\r
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")\r
+\r
+@REM Execute a user defined script before this one\r
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre\r
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"\r
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"\r
+:skipRcPre\r
+\r
+@setlocal\r
+\r
+set ERROR_CODE=0\r
+\r
+@REM To isolate internal variables from possible post scripts, we use another setlocal\r
+@setlocal\r
+\r
+@REM ==== START VALIDATION ====\r
+if not "%JAVA_HOME%" == "" goto OkJHome\r
+\r
+echo.\r
+echo Error: JAVA_HOME not found in your environment. >&2\r
+echo Please set the JAVA_HOME variable in your environment to match the >&2\r
+echo location of your Java installation. >&2\r
+echo.\r
+goto error\r
+\r
+:OkJHome\r
+if exist "%JAVA_HOME%\bin\java.exe" goto init\r
+\r
+echo.\r
+echo Error: JAVA_HOME is set to an invalid directory. >&2\r
+echo JAVA_HOME = "%JAVA_HOME%" >&2\r
+echo Please set the JAVA_HOME variable in your environment to match the >&2\r
+echo location of your Java installation. >&2\r
+echo.\r
+goto error\r
+\r
+@REM ==== END VALIDATION ====\r
+\r
+:init\r
+\r
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".\r
+@REM Fallback to current working directory if not found.\r
+\r
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir\r
+\r
+set EXEC_DIR=%CD%\r
+set WDIR=%EXEC_DIR%\r
+:findBaseDir\r
+IF EXIST "%WDIR%"\.mvn goto baseDirFound\r
+cd ..\r
+IF "%WDIR%"=="%CD%" goto baseDirNotFound\r
+set WDIR=%CD%\r
+goto findBaseDir\r
+\r
+:baseDirFound\r
+set MAVEN_PROJECTBASEDIR=%WDIR%\r
+cd "%EXEC_DIR%"\r
+goto endDetectBaseDir\r
+\r
+:baseDirNotFound\r
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r
+cd "%EXEC_DIR%"\r
+\r
+:endDetectBaseDir\r
+\r
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig\r
+\r
+@setlocal EnableExtensions EnableDelayedExpansion\r
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r
+\r
+:endReadAdditionalConfig\r
+\r
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"\r
+\r
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"\r
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r
+\r
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r
+if ERRORLEVEL 1 goto error\r
+goto end\r
+\r
+:error\r
+set ERROR_CODE=1\r
+\r
+:end\r
+@endlocal & set ERROR_CODE=%ERROR_CODE%\r
+\r
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost\r
+@REM check for post script, once with legacy .bat ending and once with .cmd ending\r
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"\r
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"\r
+:skipRcPost\r
+\r
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r
+if "%MAVEN_BATCH_PAUSE%" == "on" pause\r
+\r
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%\r
+\r
+exit /B %ERROR_CODE%\r
diff --git a/otf-camunda/pom.xml b/otf-camunda/pom.xml
new file mode 100644 (file)
index 0000000..4fa0994
--- /dev/null
@@ -0,0 +1,413 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns="http://maven.apache.org/POM/4.0.0"\r
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">\r
+  <modelVersion>4.0.0</modelVersion>\r
+\r
+  <groupId>org.oran.otf</groupId>\r
+  <artifactId>otf-camunda</artifactId>\r
+  <version>Camille.1.1</version>\r
+  <packaging>jar</packaging>\r
+  <dependencies>\r
+\r
+    <dependency>\r
+      <groupId>com.github.tomakehurst</groupId>\r
+      <artifactId>wiremock-jre8</artifactId>\r
+      <version>2.24.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.mockito</groupId>\r
+      <artifactId>mockito-core</artifactId>\r
+      <version>2.15.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.mockito</groupId>\r
+      <artifactId>mockito-inline</artifactId>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>io.rest-assured</groupId>\r
+      <artifactId>rest-assured</artifactId>\r
+      <version>4.0.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>io.rest-assured</groupId>\r
+      <artifactId>rest-assured-all</artifactId>\r
+      <version>4.0.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+\r
+\r
+\r
+    <!-- Camunda BPM dependencies -->\r
+    <dependency>\r
+      <groupId>org.camunda.bpm</groupId>\r
+      <artifactId>camunda-engine</artifactId>\r
+      <version>${camunda.bpm.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm</groupId>\r
+      <artifactId>camunda-engine-plugin-spin</artifactId>\r
+      <version>${camunda.bpm.base.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm</groupId>\r
+      <artifactId>camunda-engine-plugin-connect</artifactId>\r
+      <version>${camunda.bpm.base.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.connect</groupId>\r
+      <artifactId>camunda-connect-connectors-all</artifactId>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm.springboot</groupId>\r
+      <artifactId>camunda-bpm-spring-boot-starter</artifactId>\r
+      <version>${camunda.springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm.springboot</groupId>\r
+      <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>\r
+      <version>${camunda.springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm.springboot</groupId>\r
+      <artifactId>camunda-bpm-spring-boot-starter-webapp-ee</artifactId>\r
+      <version>${camunda.springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>camunda-external-task-client</artifactId>\r
+      <groupId>org.camunda.bpm</groupId>\r
+      <version>${camunda.bpm.external-task-client.version}</version>\r
+    </dependency>\r
+    <!-- End Camunda BPM dependencies -->\r
+    <!-- Begin Camunda BPM extension dependencies -->\r
+    <dependency>\r
+      <groupId>org.camunda.bpm.extension.mockito</groupId>\r
+      <artifactId>camunda-bpm-mockito</artifactId>\r
+      <version>${camunda.mockito.version}</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm.extension.reactor</groupId>\r
+      <artifactId>camunda-bpm-reactor-core</artifactId>\r
+      <version>${camunda.bpm.reactor.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>camunda-bpm-reactor-spring</artifactId>\r
+      <groupId>org.camunda.bpm.extension.reactor</groupId>\r
+      <version>${camunda.bpm.reactor.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.bpm.extension</groupId>\r
+      <artifactId>camunda-bpm-assert</artifactId>\r
+      <version>${camunda.bpm.assert.version}</version>\r
+    </dependency>\r
+<!--g-->\r
+    <!-- End Camunda BPM extension dependencies -->\r
+    <!-- Begin Camunda BPM Spin dependencies -->\r
+    <dependency>\r
+      <groupId>org.camunda.spin</groupId>\r
+      <artifactId>camunda-spin-core</artifactId>\r
+      <version>${camunda.spin.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.camunda.spin</groupId>\r
+      <artifactId>camunda-spin-dataformat-all</artifactId>\r
+      <version>${camunda.spin.version}</version>\r
+    </dependency>\r
+    <!-- End Camunda BPM Spin dependencies -->\r
+    <!-- Begin Spring Boot dependencies -->\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-actuator</artifactId>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>spring-boot-starter-amqp</artifactId>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-jersey</artifactId>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-data-mongodb</artifactId>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-test</artifactId>\r
+      <scope>test</scope>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <!--    <dependency>-->\r
+    <!--      <groupId>org.springframework.boot</groupId>-->\r
+    <!--      <artifactId>spring-boot-starter-web</artifactId>-->\r
+    <!--      <version>${springboot.version}</version>-->\r
+    <!--    </dependency>-->\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-configuration-processor</artifactId>\r
+      <version>${springboot.version}</version>\r
+      <optional>true</optional>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-json</artifactId>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <artifactId>spring-boot-starter-jdbc</artifactId>\r
+      <version>${springboot.version}</version>\r
+    </dependency>\r
+    <!-- End Spring Boot dependencies -->\r
+    <!-- Begin CADI AAF -->\r
+    <dependency>\r
+      <groupId>org.onap.aaf.authz</groupId>\r
+      <artifactId>aaf-auth-client</artifactId>\r
+      <version>${cadi.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.onap.aaf.authz</groupId>\r
+      <artifactId>aaf-cadi-core</artifactId>\r
+      <version>${cadi.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.onap.aaf.authz</groupId>\r
+      <artifactId>aaf-cadi-aaf</artifactId>\r
+      <version>${cadi.version}</version>\r
+    </dependency>\r
+    <!-- End CADI AAF -->\r
+    <dependency>\r
+      <groupId>com.h2database</groupId>\r
+      <artifactId>h2</artifactId>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.apache.commons</groupId>\r
+      <artifactId>commons-lang3</artifactId>\r
+      <version>3.4</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.codehaus.groovy</groupId>\r
+      <artifactId>groovy-all</artifactId>\r
+      <version>${groovy.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>com.google.code.gson</groupId>\r
+      <artifactId>gson</artifactId>\r
+      <version>${google.gson.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>com.google.guava</groupId>\r
+      <artifactId>guava</artifactId>\r
+      <version>${google.guava.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.python</groupId>\r
+      <artifactId>jython-standalone</artifactId>\r
+      <version>${python.version}</version>\r
+    </dependency>\r
+    <!-- MySQL Connector -->\r
+    <dependency>\r
+      <groupId>mysql</groupId>\r
+      <artifactId>mysql-connector-java</artifactId>\r
+      <version>8.0.14</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>de.flapdoodle.embed.mongo</artifactId>\r
+      <groupId>de.flapdoodle.embed</groupId>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>jersey-media-multipart</artifactId>\r
+      <groupId>org.glassfish.jersey.media</groupId>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>junit</artifactId>\r
+      <groupId>junit</groupId>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>httpclient</artifactId>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+      <version>4.5.7</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>httpasyncclient</artifactId>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+      <version>4.1.4</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>jackson-module-kotlin</artifactId>\r
+      <groupId>com.fasterxml.jackson.module</groupId>\r
+      <version>${jackson.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <artifactId>jackson-datatype-jsr310</artifactId>\r
+      <groupId>com.fasterxml.jackson.datatype</groupId>\r
+      <version>${jackson.version}</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.apache.commons</groupId>\r
+      <artifactId>commons-vfs2</artifactId>\r
+      <version>2.2</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>com.jcraft</groupId>\r
+      <artifactId>jsch</artifactId>\r
+      <version>0.1.54</version>\r
+    </dependency>\r
+  </dependencies>\r
+  <parent>\r
+    <groupId>org.springframework.boot</groupId>\r
+    <artifactId>spring-boot-starter-parent</artifactId>\r
+    <version>2.1.4.RELEASE</version>\r
+  </parent>\r
+  <dependencyManagement>\r
+    <dependencies>\r
+      <dependency>\r
+        <!-- Import dependency management from camunda -->\r
+        <groupId>org.camunda.bpm</groupId>\r
+        <artifactId>camunda-bom</artifactId>\r
+        <version>${camunda.version}</version>\r
+        <scope>import</scope>\r
+        <type>pom</type>\r
+      </dependency>\r
+    </dependencies>\r
+  </dependencyManagement>\r
+  <modules>\r
+  </modules>\r
+  <properties>\r
+    <!-- Refer to the Camunda version compatibility matrix for choosing a version for a Spring Boot\r
+    Starter, Camunda BPM, and Spring Boot. -->\r
+    <skipTests>false</skipTests>\r
+    <skipITs>${skipTests}</skipITs>\r
+    <skipUTs>${skipTests}</skipUTs>\r
+\r
+    <cadi.version>2.1.10</cadi.version>\r
+    <docker.registry>registry.hub.docker.io</docker.registry>\r
+    <camunda.version>7.10.0-ee</camunda.version>\r
+    <camunda.bpm.assert.version>2.0-alpha2</camunda.bpm.assert.version>\r
+    <camunda.bpm.base.version>7.10.0</camunda.bpm.base.version>\r
+    <camunda.bpm.mail.version>1.1.0</camunda.bpm.mail.version>\r
+    <camunda.bpm.reactor.version>2.1.2</camunda.bpm.reactor.version>\r
+    <camunda.bpm.version>7.10.4-ee</camunda.bpm.version>\r
+    <camunda.bpm.external-task-client.version>1.1.1</camunda.bpm.external-task-client.version>\r
+    <camunda.mockito.version>3.2.1</camunda.mockito.version>\r
+    <camunda.spin.version>1.6.6</camunda.spin.version>\r
+    <camunda.springboot.version>3.2.0</camunda.springboot.version>\r
+    <google.guava.version>27.1-jre</google.guava.version>\r
+    <google.gson.version>2.8.5</google.gson.version>\r
+    <groovy.version>2.1.3</groovy.version>\r
+    <jackson.version>2.9.5</jackson.version>\r
+    <python.version>2.7.1</python.version>\r
+    <springboot.version>2.1.4.RELEASE</springboot.version>\r
+  </properties>\r
+  <build>\r
+    <finalName>otf-camunda</finalName>\r
+    <plugins>\r
+      <plugin>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+        <artifactId>maven-compiler-plugin</artifactId>\r
+        <configuration>\r
+          <source>1.8</source>\r
+          <target>1.8</target>\r
+        </configuration>\r
+      </plugin>\r
+      <plugin>\r
+        <groupId>org.springframework.boot</groupId>\r
+        <artifactId>spring-boot-maven-plugin</artifactId>\r
+        <version>${springboot.version}</version>\r
+        <configuration>\r
+          <requiresUnpack>\r
+            <dependency>\r
+              <groupId>org.python</groupId>\r
+              <artifactId>jython-standalone</artifactId>\r
+            </dependency>\r
+          </requiresUnpack>\r
+        </configuration>\r
+      </plugin>\r
+\r
+      <plugin>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+        <artifactId>maven-surefire-plugin</artifactId>\r
+        <version>2.22.1</version>\r
+        <configuration>\r
+          <skipTests>${skipUTs}</skipTests>\r
+        </configuration>\r
+      </plugin>\r
+      <plugin>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+        <artifactId>maven-failsafe-plugin</artifactId>\r
+        <version>2.22.1</version>\r
+        <executions>\r
+          <execution>\r
+            <id>run-integration-tests</id>\r
+            <phase>integration-test</phase>\r
+            <goals>\r
+              <goal>verify</goal>\r
+            </goals>\r
+          </execution>\r
+        </executions>\r
+        <configuration>\r
+          <skipTests>${skipTests}</skipTests>\r
+          <skipITs>${skipITs}</skipITs>\r
+        </configuration>\r
+      </plugin>\r
+\r
+    </plugins>\r
+    <resources>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <targetPath>${basedir}/target/src/main/resources</targetPath>\r
+        <filtering>true</filtering>\r
+        <includes>\r
+          <include>**/*</include>\r
+        </includes>\r
+        <excludes>\r
+          <exclude>otf_dev.p12</exclude>\r
+        </excludes>\r
+      </resource>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <filtering>true</filtering>\r
+        <includes>\r
+          <include>**/*</include>\r
+        </includes>\r
+        <excludes>\r
+          <exclude>otf_dev.p12</exclude>\r
+        </excludes>\r
+      </resource>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <targetPath>${basedir}/target/src/main/resources</targetPath>\r
+        <includes>\r
+          <include>otf_dev.p12</include>\r
+        </includes>\r
+      </resource>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <includes>\r
+          <include>otf_dev.p12</include>\r
+        </includes>\r
+      </resource>\r
+      <resource>\r
+        <directory>docker</directory>\r
+        <targetPath>${basedir}/target</targetPath>\r
+        <includes>\r
+          <include>Dockerfile</include>\r
+        </includes>\r
+      </resource>\r
+    </resources>\r
+  </build>\r
+  <name>otf-camunda</name>\r
+  <description>One of the core components of the Open Test Framework Test Control Unit.\r
+  </description>\r
+\r
+</project>
\ No newline at end of file
diff --git a/otf-camunda/src/main/java/org/oran/otf/Application.java b/otf-camunda/src/main/java/org/oran/otf/Application.java
new file mode 100644 (file)
index 0000000..7dfa547
--- /dev/null
@@ -0,0 +1,100 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf;\r
+\r
+import java.util.List;\r
+import java.util.concurrent.Executor;\r
+import org.camunda.bpm.application.PostDeploy;\r
+import org.camunda.bpm.application.PreUndeploy;\r
+import org.camunda.bpm.application.ProcessApplicationInfo;\r
+import org.camunda.bpm.engine.ProcessEngine;\r
+import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.SpringApplication;\r
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.SpringBootApplication;\r
+import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.ComponentScan;\r
+import org.springframework.context.annotation.Primary;\r
+import org.springframework.scheduling.annotation.EnableAsync;\r
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\r
+\r
+@SpringBootApplication\r
+@EnableAsync\r
+@ComponentScan(basePackages = "org.oran.otf")\r
+@EnableProcessApplication\r
+@EnableAutoConfiguration(\r
+    exclude = {\r
+        ErrorMvcAutoConfiguration.class,\r
+        DataSourceAutoConfiguration.class,\r
+        HibernateJpaAutoConfiguration.class,\r
+        MongoDataAutoConfiguration.class,\r
+        MongoAutoConfiguration.class\r
+    })\r
+public class Application {\r
+  public static void main(String[] args) {\r
+    SpringApplication.run(Application.class, args);\r
+\r
+  }\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(Application.class);\r
+\r
+  @Value("${otf.camunda.executor.async.core-pool-size}")\r
+  private int corePoolSize;\r
+\r
+  @Value("${otf.camunda.executor.async.max-pool-size}")\r
+  private int maxPoolSize;\r
+\r
+  @Value("${otf.camunda.executor.async.queue-capacity}")\r
+  private int queueCapacity;\r
+\r
+  private static final String LOGS_DIR = "logs_dir";\r
+\r
+\r
+  private static void setLogsDir() {\r
+    if (System.getProperty(LOGS_DIR) == null) {\r
+      System.getProperties().setProperty(LOGS_DIR, "./logs/camunda/");\r
+    }\r
+  }\r
+\r
+  @PostDeploy\r
+  public void postDeploy(ProcessEngine processEngineInstance) {}\r
+\r
+  @PreUndeploy\r
+  public void cleanup(ProcessEngine processEngine, ProcessApplicationInfo processApplicationInfo,\r
+      List<ProcessEngine> processEngines) {}\r
+\r
+  @Bean\r
+  @Primary\r
+  public Executor asyncExecutor() {\r
+    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\r
+    //executor.setTaskDecorator(new MDCTaskDecorator());\r
+    executor.setCorePoolSize(corePoolSize);\r
+    executor.setMaxPoolSize(maxPoolSize);\r
+    executor.setQueueCapacity(queueCapacity);\r
+    executor.setThreadNamePrefix("Camunda-");\r
+    executor.initialize();\r
+    return executor;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/CadiFilterConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/CadiFilterConfiguration.java
new file mode 100644 (file)
index 0000000..d0b09ec
--- /dev/null
@@ -0,0 +1,97 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.cadi.configuration;\r
+\r
+import javax.servlet.Filter;\r
+import org.onap.aaf.cadi.Access.Level;\r
+import org.onap.aaf.cadi.config.Config;\r
+import org.onap.aaf.cadi.filter.CadiFilter;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.web.servlet.FilterRegistrationBean;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Conditional;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.PropertySource;\r
+\r
+@PropertySource("classpath:application.yaml")\r
+@Configuration\r
+@Conditional(value = FilterCondition.class)\r
+public class CadiFilterConfiguration {\r
+\r
+  @Value("${otf.cadi.aaf-mech-id}")\r
+  private String AAF_APPID;\r
+\r
+  @Value("${otf.cadi.aaf-mech-password}")\r
+  private String AAF_APPPASS;\r
+\r
+  @Value("${otf.cadi.hostname}")\r
+  private String CADI_HOSTNAME;\r
+\r
+  @Value("${otf.cadi.keyfile}")\r
+  private String CADI_KEYFILE;\r
+\r
+  @Value("${otf.ssl.keystore-path}")\r
+  private String CADI_KEYSTORE;\r
+\r
+  @Value("${otf.ssl.keystore-password}")\r
+  private String CADI_KEYSTORE_PASSWORD;\r
+\r
+  @Bean(name = "cadiFilterRegistrationBean")\r
+//  @ConditionalOnProperty(prefix = "otf.cadi", name = "enabled", havingValue = "true", matchIfMissing = true)\r
+  public FilterRegistrationBean<Filter> cadiFilterRegistration() {\r
+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();\r
+    // set cadi configuration properties\r
+    initCadiProperties(registration);\r
+\r
+    registration.addUrlPatterns("/otf/tcu/*", "/rest/*");\r
+    registration.setFilter(cadiFilter());\r
+    registration.setName("otfCadiFilter");\r
+    registration.setOrder(0);\r
+    return registration;\r
+  }\r
+\r
+  Filter cadiFilter() {\r
+    return new CadiFilter();\r
+  }\r
+\r
+  private void initCadiProperties(FilterRegistrationBean<Filter> registration) {\r
+    registration.addInitParameter(Config.AAF_APPID, AAF_APPID);\r
+    registration.addInitParameter(Config.AAF_APPPASS, AAF_APPPASS);\r
+    registration.addInitParameter(Config.AAF_CALL_TIMEOUT, "10000");\r
+    registration.addInitParameter(Config.AAF_CONN_TIMEOUT, "6000");\r
+    registration.addInitParameter(Config.AAF_DEFAULT_REALM, "localhost");\r
+    registration.addInitParameter(Config.AAF_ENV, "PROD");\r
+    registration.addInitParameter(Config.AAF_LOCATE_URL, "https://localhost");\r
+    registration.addInitParameter(Config.AAF_LUR_CLASS, "org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm");\r
+    registration.addInitParameter(\r
+        Config.AAF_URL, "https://localhost");\r
+\r
+    registration.addInitParameter(Config.BASIC_REALM, "localhost");\r
+    registration.addInitParameter(Config.BASIC_WARN, "true");\r
+\r
+    registration.addInitParameter(Config.CADI_KEYFILE, CADI_KEYFILE);\r
+    registration.addInitParameter(Config.CADI_LATITUDE, "38.62782");\r
+    registration.addInitParameter(Config.CADI_LOGLEVEL, Level.ERROR.name());\r
+    registration.addInitParameter(Config.CADI_LONGITUDE, "-90.19458");\r
+    registration.addInitParameter(Config.CADI_NOAUTHN, "/health/v1");\r
+    registration.addInitParameter(Config.CADI_PROTOCOLS, "TLSv1.1,TLSv1.2");\r
+    registration.addInitParameter(Config.CADI_KEYSTORE, CADI_KEYSTORE);\r
+    registration.addInitParameter(Config.CADI_KEYSTORE_PASSWORD, CADI_KEYSTORE_PASSWORD);\r
+\r
+    registration.addInitParameter(Config.HOSTNAME, CADI_HOSTNAME);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/FilterCondition.java b/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/FilterCondition.java
new file mode 100644 (file)
index 0000000..d1f1515
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.cadi.configuration;\r
+\r
+import org.springframework.context.annotation.Condition;\r
+import org.springframework.context.annotation.ConditionContext;\r
+import org.springframework.core.type.AnnotatedTypeMetadata;\r
+\r
+import java.lang.annotation.Annotation;\r
+\r
+public class FilterCondition implements Condition {\r
+    @Override\r
+    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {\r
+        String enabled = conditionContext.getEnvironment().getProperty("otf.cadi.enabled");\r
+        if (enabled == null)\r
+            return true;\r
+        return !enabled.equalsIgnoreCase("false");\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/OTFApiEnforcementFilterConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/OTFApiEnforcementFilterConfiguration.java
new file mode 100644 (file)
index 0000000..6649b12
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.cadi.configuration;\r
+\r
+import org.oran.otf.cadi.filter.OTFApiEnforcementFilter;\r
+import javax.servlet.Filter;\r
+import javax.servlet.FilterConfig;\r
+import javax.servlet.ServletException;\r
+import org.onap.aaf.cadi.Access;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.web.servlet.FilterRegistrationBean;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Conditional;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.PropertySource;\r
+\r
+@PropertySource("classpath:application.yaml")\r
+@Configuration\r
+@Conditional(value = FilterCondition.class)\r
+public class OTFApiEnforcementFilterConfiguration {\r
+\r
+  @Value("${otf.cadi.aaf-perm-type}")\r
+  private String AAF_PERM_TYPE;\r
+\r
+  private Access access;\r
+  private FilterConfig fc;\r
+\r
+  @Bean(name = "otfApiEnforcementFilterRegistrationBean")\r
+//  @ConditionalOnProperty(prefix ="otf.cadi", name ="enabled", havingValue = "true" ,matchIfMissing = true)\r
+  @Conditional(value = FilterCondition.class)\r
+  public FilterRegistrationBean<Filter> otfApiEnforcementFilterRegistration()\r
+      throws ServletException {\r
+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();\r
+    initFilterParameters(registration);\r
+\r
+    registration.addUrlPatterns("/otf/tcu/*", "/rest/*");\r
+    registration.setFilter(otfApiEnforcementFilter());\r
+    registration.setName("otfApiEnforcementFilter");\r
+    registration.setOrder(1);\r
+    return registration;\r
+  }\r
+\r
+  @Bean(name = "otfApiEnforcementFilter")\r
+  @Conditional(value = FilterCondition.class)\r
+//  @ConditionalOnProperty(prefix ="otf.cadi", name ="enabled", havingValue = "true", matchIfMissing = true)\r
+  Filter otfApiEnforcementFilter() throws ServletException {\r
+    return new OTFApiEnforcementFilter(access, AAF_PERM_TYPE);\r
+  }\r
+\r
+  private void initFilterParameters(FilterRegistrationBean<Filter> registration) {\r
+    registration.addInitParameter("aaf_perm_type", AAF_PERM_TYPE);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/cadi/filter/OTFApiEnforcementFilter.java b/otf-camunda/src/main/java/org/oran/otf/cadi/filter/OTFApiEnforcementFilter.java
new file mode 100644 (file)
index 0000000..cf04193
--- /dev/null
@@ -0,0 +1,134 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.cadi.filter;\r
+\r
+import com.google.common.base.Strings;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.TreeMap;\r
+import javax.servlet.Filter;\r
+import javax.servlet.FilterChain;\r
+import javax.servlet.FilterConfig;\r
+import javax.servlet.ServletException;\r
+import javax.servlet.ServletRequest;\r
+import javax.servlet.ServletResponse;\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+import org.onap.aaf.cadi.Access;\r
+import org.onap.aaf.cadi.Access.Level;\r
+import org.onap.aaf.cadi.ServletContextAccess;\r
+import org.onap.aaf.cadi.util.Split;\r
+\r
+public class OTFApiEnforcementFilter implements Filter {\r
+  private static final Log log = LogFactory.getLog(OTFApiEnforcementFilter.class);\r
+  private String type;\r
+  private Map<String, List<String>> publicPaths;\r
+  private Access access = null;\r
+\r
+  public OTFApiEnforcementFilter(Access access, String enforce) throws ServletException {\r
+    this.access = access;\r
+    init(enforce);\r
+  }\r
+\r
+  @Override\r
+  public void init(FilterConfig fc) throws ServletException {\r
+    init(fc.getInitParameter("aaf_perm_type"));\r
+    // need the Context for Logging, instantiating ClassLoader, etc\r
+    ServletContextAccess sca = new ServletContextAccess(fc);\r
+    if (access == null) {\r
+      access = sca;\r
+    }\r
+  }\r
+\r
+  private void init(final String ptypes) throws ServletException {\r
+    if (Strings.isNullOrEmpty(ptypes)) {\r
+      throw new ServletException("OTFApiEnforcement requires aaf_perm_type property");\r
+    }\r
+    String[] full = Split.splitTrim(';', ptypes);\r
+    if (full.length <= 0) {\r
+      throw new ServletException("aaf_perm_type property is empty");\r
+    }\r
+\r
+    type = full[0];\r
+    publicPaths = new TreeMap<>();\r
+    if (full.length > 1) {\r
+      for (int i = 1; i < full.length; ++i) {\r
+        String[] pubArray = Split.split(':', full[i]);\r
+        if (pubArray.length == 2) {\r
+          List<String> ls = publicPaths.get(pubArray[0]);\r
+          if (ls == null) {\r
+            ls = new ArrayList<>();\r
+            publicPaths.put(pubArray[0], ls);\r
+          }\r
+          ls.add(pubArray[1]);\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc)\r
+      throws IOException, ServletException {\r
+    HttpServletRequest hreq = (HttpServletRequest) req;\r
+    final String meth = hreq.getMethod();\r
+    String path = hreq.getContextPath(); // + hreq.getPathInfo();\r
+\r
+    if (Strings.isNullOrEmpty(path) || "null".equals(path)) {\r
+      path = hreq.getRequestURI().substring(hreq.getContextPath().length());\r
+    }\r
+\r
+    List<String> list = publicPaths.get(meth);\r
+    if (list != null) {\r
+      for (String p : publicPaths.get(meth)) {\r
+        if (path.startsWith(p)) {\r
+          access.printf(\r
+              Level.INFO,\r
+              "%s accessed public API %s %s\n",\r
+              hreq.getUserPrincipal().getName(),\r
+              meth,\r
+              path);\r
+          fc.doFilter(req, resp);\r
+          return;\r
+        }\r
+      }\r
+    }\r
+    if (hreq.isUserInRole(type + '|' + path + '|' + meth)) {\r
+      access.printf(\r
+          Level.INFO,\r
+          "%s is allowed access to %s %s\n",\r
+          hreq.getUserPrincipal().getName(),\r
+          meth,\r
+          path);\r
+      fc.doFilter(req, resp);\r
+    } else {\r
+      access.printf(\r
+          Level.AUDIT,\r
+          "%s is denied access to %s %s\n",\r
+          hreq.getUserPrincipal().getName(),\r
+          meth,\r
+          path);\r
+      ((HttpServletResponse) resp).sendError(HttpServletResponse.SC_UNAUTHORIZED);\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public void destroy() {}\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFAuthorizationConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFAuthorizationConfiguration.java
new file mode 100644 (file)
index 0000000..f79550b
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+public class OTFAuthorizationConfiguration {\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDataSourceConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDataSourceConfiguration.java
new file mode 100644 (file)
index 0000000..87936f5
--- /dev/null
@@ -0,0 +1,63 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+import javax.sql.DataSource;\r
+\r
+import com.zaxxer.hikari.HikariDataSource;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.jdbc.DataSourceBuilder;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Primary;\r
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;\r
+import org.springframework.transaction.PlatformTransactionManager;\r
+\r
+@Configuration\r
+public class OTFDataSourceConfiguration {\r
+  @Value("${otf.camunda.mysql.url}")\r
+  private String url;\r
+\r
+  @Value("${otf.camunda.mysql.username}")\r
+  private String username;\r
+\r
+  @Value("${otf.camunda.mysql.password}")\r
+  private String password;\r
+\r
+  @Bean\r
+  @Primary\r
+  public DataSource dataSource() {\r
+    DataSource dataSource = DataSourceBuilder.create()\r
+            .url(url)\r
+            .username(username)\r
+            .password(password)\r
+            .driverClassName("com.mysql.cj.jdbc.Driver")\r
+            .build();\r
+    if (dataSource instanceof HikariDataSource){\r
+//      ((HikariDataSource) dataSource).setLeakDetectionThreshold(10000);\r
+\r
+      ((HikariDataSource) dataSource).setMaximumPoolSize(75);\r
+      ((HikariDataSource) dataSource).setMinimumIdle(15);\r
+    }\r
+    return dataSource;\r
+  }\r
+\r
+  @Bean\r
+  public PlatformTransactionManager transactionManager() {\r
+    return new DataSourceTransactionManager(dataSource());\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDeploymentConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDeploymentConfiguration.java
new file mode 100644 (file)
index 0000000..40d2a74
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+public class OTFDeploymentConfiguration {\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFFailedJobConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFFailedJobConfiguration.java
new file mode 100644 (file)
index 0000000..a8b62f9
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+public class OTFFailedJobConfiguration {\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJacksonDataConfigurator.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJacksonDataConfigurator.java
new file mode 100644 (file)
index 0000000..11b8705
--- /dev/null
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\r
+import com.fasterxml.jackson.module.kotlin.KotlinModule;\r
+import org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat;\r
+import org.camunda.spin.spi.DataFormatConfigurator;\r
+import org.springframework.context.annotation.Configuration;\r
+import spinjar.com.fasterxml.jackson.databind.ObjectMapper;\r
+import spinjar.com.fasterxml.jackson.databind.module.SimpleModule;\r
+\r
+@Configuration\r
+public class OTFJacksonDataConfigurator implements DataFormatConfigurator<JacksonJsonDataFormat> {\r
+\r
+  @Override\r
+  public Class<JacksonJsonDataFormat> getDataFormatClass() {\r
+    return JacksonJsonDataFormat.class;\r
+  }\r
+\r
+  @Override\r
+  public void configure(JacksonJsonDataFormat dataFormat) {\r
+    ObjectMapper mapper = dataFormat.getObjectMapper();\r
+    SimpleModule module = new SimpleModule();\r
+    module.registerSubtypes(KotlinModule.class);\r
+    module.registerSubtypes(JavaTimeModule.class);\r
+    mapper.registerModule(module);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJobConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJobConfiguration.java
new file mode 100644 (file)
index 0000000..6ece823
--- /dev/null
@@ -0,0 +1,73 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+import org.oran.otf.camunda.configuration.listener.OTFJobExecutorStartingEventListener;\r
+import org.camunda.bpm.engine.impl.jobexecutor.JobExecutor;\r
+import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration;\r
+import org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultJobConfiguration;\r
+import org.camunda.bpm.spring.boot.starter.event.JobExecutorStartingEventListener;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\r
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Primary;\r
+\r
+@Configuration\r
+public class OTFJobConfiguration extends DefaultJobConfiguration {\r
+  @Autowired protected JobExecutor jobExecutor;\r
+\r
+  @Override\r
+  protected void configureJobExecutor(SpringProcessEngineConfiguration configuration) {\r
+    int podNumber = -1;\r
+    String[] hostnameSplit = {"0", "0", "0"};\r
+\r
+    try {\r
+      String hostname = System.getenv("HOSTNAME");\r
+      hostnameSplit = hostname.split("-");\r
+      podNumber = Integer.parseInt(hostnameSplit[2]);\r
+    } catch (Exception e) {\r
+      podNumber = 1;\r
+    }\r
+\r
+    //if (podNumber == 1) {\r
+      camundaBpmProperties.getJobExecution().setLockTimeInMillis(43200000);\r
+      camundaBpmProperties.getJobExecution().setBackoffTimeInMillis(90);\r
+      camundaBpmProperties.getJobExecution().setMaxBackoff(450L);\r
+      camundaBpmProperties.getJobExecution().setWaitIncreaseFactor(2f);\r
+\r
+      super.configureJobExecutor(configuration);\r
+\r
+      configuration.getJobExecutor().setLockTimeInMillis(43200000);\r
+      configuration.getJobExecutor().setBackoffTimeInMillis(90);\r
+      configuration.getJobExecutor().setMaxBackoff(450L);\r
+      configuration.getJobExecutor().setWaitIncreaseFactor(2);\r
+\r
+\r
+      // configuration.getJobExecutor().setAutoActivate(false);\r
+   // }\r
+  }\r
+\r
+  @Bean\r
+  @Primary\r
+  @ConditionalOnProperty(prefix = "camunda.bpm.job-execution", name = "enabled", havingValue = "true", matchIfMissing = true)\r
+  @ConditionalOnBean(JobExecutor.class)\r
+  public static JobExecutorStartingEventListener jobExecutorStartingEventListener() {\r
+    return new OTFJobExecutorStartingEventListener();\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFLoggingFeature.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFLoggingFeature.java
new file mode 100644 (file)
index 0000000..9715fc0
--- /dev/null
@@ -0,0 +1,238 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+import org.glassfish.jersey.logging.LoggingFeature;\r
+import org.glassfish.jersey.message.MessageUtils;\r
+\r
+import javax.ws.rs.WebApplicationException;\r
+import javax.ws.rs.client.ClientRequestContext;\r
+import javax.ws.rs.client.ClientRequestFilter;\r
+import javax.ws.rs.client.ClientResponseContext;\r
+import javax.ws.rs.client.ClientResponseFilter;\r
+import javax.ws.rs.container.ContainerRequestContext;\r
+import javax.ws.rs.container.ContainerRequestFilter;\r
+import javax.ws.rs.container.ContainerResponseContext;\r
+import javax.ws.rs.container.ContainerResponseFilter;\r
+import javax.ws.rs.core.FeatureContext;\r
+import javax.ws.rs.core.MultivaluedMap;\r
+import javax.ws.rs.ext.WriterInterceptor;\r
+import javax.ws.rs.ext.WriterInterceptorContext;\r
+import java.io.*;\r
+import java.net.URI;\r
+import java.nio.charset.Charset;\r
+import java.util.ArrayList;\r
+import java.util.Base64;\r
+import java.util.List;\r
+import java.util.Objects;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+\r
+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,\r
+        ClientRequestFilter, ClientResponseFilter, WriterInterceptor {\r
+\r
+    private static final boolean printEntity = true;\r
+    private static final int maxEntitySize = 8 * 1024;\r
+    private final Logger logger = Logger.getLogger("OTFLoggingFeature");\r
+    private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();\r
+    private static final String NOTIFICATION_PREFIX = "* ";\r
+    private static final String REQUEST_PREFIX = "> ";\r
+    private static final String RESPONSE_PREFIX = "< ";\r
+    private static final String AUTHORIZATION = "Authorization";\r
+    private static final String EQUAL = " = ";\r
+    private static final String HEADERS_SEPARATOR = ", ";\r
+    private static List<String> requestHeaders;\r
+\r
+    static {\r
+        requestHeaders = new ArrayList<>();\r
+        requestHeaders.add(AUTHORIZATION);\r
+    }\r
+\r
+    public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {\r
+        super(logger, level, verbosity, maxEntitySize);\r
+    }\r
+\r
+    @Override\r
+    public boolean configure(FeatureContext context) {\r
+        context.register(this);\r
+        return true;\r
+    }\r
+\r
+    private Object getEmail(Object authorization){\r
+        try{\r
+            String encoded = ((String) authorization).split(" ")[1];\r
+            String decoded =  new String(Base64.getDecoder().decode(encoded));\r
+            return decoded.split(":")[0];\r
+        }\r
+        catch (Exception e){\r
+            return authorization;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ClientRequestContext context) {\r
+        final StringBuilder b = new StringBuilder();\r
+        printHeaders(b, context.getStringHeaders());\r
+        printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());\r
+\r
+        if (printEntity && context.hasEntity()) {\r
+            final OutputStream stream = new LoggingStream(b, context.getEntityStream());\r
+            context.setEntityStream(stream);\r
+            context.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+            // not calling log(b) here - it will be called by the interceptor\r
+        } else {\r
+            log(b);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {\r
+        final StringBuilder b = new StringBuilder();\r
+        printResponseLine(b, "Client response received", responseContext.getStatus());\r
+\r
+        if (printEntity && responseContext.hasEntity()) {\r
+            responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),\r
+                    MessageUtils.getCharset(responseContext.getMediaType())));\r
+        }\r
+        log(b);\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ContainerRequestContext context) throws IOException {\r
+        final StringBuilder b = new StringBuilder();\r
+        printHeaders(b, context.getHeaders());\r
+        printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());\r
+\r
+        if (printEntity && context.hasEntity()) {\r
+            context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));\r
+        }\r
+        log(b);\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {\r
+        final StringBuilder b = new StringBuilder();\r
+        printResponseLine(b, "Server responded with a response", responseContext.getStatus());\r
+\r
+        if (printEntity && responseContext.hasEntity()) {\r
+            final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());\r
+            responseContext.setEntityStream(stream);\r
+            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+            // not calling log(b) here - it will be called by the interceptor\r
+        } else {\r
+            log(b);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {\r
+        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);\r
+        writerInterceptorContext.proceed();\r
+        if (stream != null) {\r
+            log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));\r
+        }\r
+    }\r
+\r
+    private static class LoggingStream extends FilterOutputStream {\r
+        private final StringBuilder b;\r
+        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\r
+\r
+        LoggingStream(final StringBuilder b, final OutputStream inner) {\r
+            super(inner);\r
+\r
+            this.b = b;\r
+        }\r
+\r
+        StringBuilder getStringBuilder(Charset charset) {\r
+            // write entity to the builder\r
+            final byte[] entity = byteArrayOutputStream.toByteArray();\r
+\r
+            b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));\r
+            if (entity.length > maxEntitySize) {\r
+                b.append("...more...");\r
+            }\r
+            b.append('\n');\r
+\r
+            return b;\r
+        }\r
+\r
+        public void write(final int i) throws IOException {\r
+            if (byteArrayOutputStream.size() <= maxEntitySize) {\r
+                byteArrayOutputStream.write(i);\r
+            }\r
+            out.write(i);\r
+        }\r
+    }\r
+\r
+    private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {\r
+        for (String header : requestHeaders) {\r
+            if (Objects.nonNull(headers.get(header))) {\r
+                if(header.equalsIgnoreCase("Authorization")){\r
+                    b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);\r
+                }\r
+                else{\r
+                    b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);\r
+                }\r
+            }\r
+        }\r
+        int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);\r
+        if (lastIndex != -1) {\r
+            b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());\r
+            b.append("\n");\r
+        }\r
+    }\r
+\r
+    private void log(final StringBuilder b) {\r
+        String message = b.toString();\r
+        if (logger != null) {\r
+            logger.info(message);\r
+        }\r
+    }\r
+\r
+    private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {\r
+        b.append(NOTIFICATION_PREFIX)\r
+                .append(note)\r
+                .append(" on thread ").append(Thread.currentThread().getId())\r
+                .append(REQUEST_PREFIX).append(method).append(" ")\r
+                .append(uri.toASCIIString()).append("\n");\r
+    }\r
+\r
+    private void printResponseLine(final StringBuilder b, final String note, final int status) {\r
+        b.append(NOTIFICATION_PREFIX)\r
+                .append(note)\r
+                .append(" on thread ").append(Thread.currentThread().getId())\r
+                .append(RESPONSE_PREFIX)\r
+                .append(Integer.toString(status))\r
+                .append("\n");\r
+    }\r
+\r
+    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {\r
+        if (!stream.markSupported()) {\r
+            stream = new BufferedInputStream(stream);\r
+        }\r
+        stream.mark(maxEntitySize + 1);\r
+        final byte[] entity = new byte[maxEntitySize + 1];\r
+        final int entitySize = stream.read(entity);\r
+        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));\r
+        if (entitySize > maxEntitySize) {\r
+            b.append("...more...");\r
+        }\r
+        b.append('\n');\r
+        stream.reset();\r
+        return stream;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OtfCamundaConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OtfCamundaConfiguration.java
new file mode 100644 (file)
index 0000000..a816786
--- /dev/null
@@ -0,0 +1,155 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration;\r
+\r
+import com.google.common.base.Strings;\r
+import java.util.Optional;\r
+import java.util.UUID;\r
+import javax.sql.DataSource;\r
+import org.camunda.bpm.application.impl.event.ProcessApplicationEventListenerPlugin;\r
+import org.camunda.bpm.engine.ProcessEngineConfiguration;\r
+import org.camunda.bpm.engine.impl.cfg.IdGenerator;\r
+import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;\r
+import org.camunda.bpm.engine.impl.history.HistoryLevel;\r
+import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration;\r
+import org.camunda.bpm.extension.reactor.bus.CamundaEventBus;\r
+import org.camunda.bpm.extension.reactor.plugin.ReactorProcessEnginePlugin;\r
+import org.camunda.bpm.extension.reactor.projectreactor.EventBus;\r
+import org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultProcessEngineConfiguration;\r
+import org.camunda.connect.plugin.impl.ConnectProcessEnginePlugin;\r
+import org.camunda.spin.plugin.impl.SpinProcessEnginePlugin;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.beans.factory.annotation.Qualifier;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.transaction.PlatformTransactionManager;\r
+import org.springframework.util.StringUtils;\r
+\r
+@Configuration\r
+public class OtfCamundaConfiguration extends DefaultProcessEngineConfiguration {\r
+\r
+  @Autowired\r
+  private DataSource dataSource;\r
+  @Autowired\r
+  private PlatformTransactionManager transactionManager;\r
+  @Autowired private Optional<IdGenerator> idGenerator;\r
+\r
+  public static String processEngineName;\r
+\r
+  @Bean\r
+  public ProcessEngineConfiguration configureEngine(ProcessEngineConfigurationImpl configuration) {\r
+    configuration.setJavaSerializationFormatEnabled(true);\r
+    return configuration;\r
+  }\r
+\r
+  @Override\r
+  public void preInit(SpringProcessEngineConfiguration configuration) {\r
+\r
+    logger.info(configuration.getProcessEngineName());\r
+    processEngineName = System.getenv("HOSTNAME");\r
+    if (Strings.isNullOrEmpty(processEngineName)) {\r
+      processEngineName = "otf-camunda-" + UUID.randomUUID().toString();\r
+    }\r
+    processEngineName = processEngineName.replaceAll("-", "_");\r
+    camundaBpmProperties.setProcessEngineName(processEngineName);\r
+    camundaBpmProperties.setAutoDeploymentEnabled(true);\r
+    camundaBpmProperties.setHistoryLevel(HistoryLevel.HISTORY_LEVEL_FULL.getName());\r
+    camundaBpmProperties.setDefaultNumberOfRetries(1);\r
+\r
+    setProcessEngineName(configuration);\r
+    setDefaultSerializationFormat(configuration);\r
+    setIdGenerator(configuration);\r
+    setJobExecutorAcquireByPriority(configuration);\r
+    setDefaultNumberOfRetries(configuration);\r
+\r
+    configuration.setDataSource(dataSource);\r
+    configuration.setTransactionManager(transactionManager);\r
+    configuration.setHistory("true");\r
+    configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);\r
+    configuration.setJobExecutorActivate(true);\r
+    configuration.setCreateIncidentOnFailedJobEnabled(true);\r
+    configuration.setFailedJobListenerMaxRetries(0);\r
+    configuration.setJavaSerializationFormatEnabled(true);\r
+    configuration.setMetricsEnabled(false);\r
+  }\r
+\r
+  private void setIdGenerator(SpringProcessEngineConfiguration configuration) {\r
+    idGenerator.ifPresent(configuration::setIdGenerator);\r
+  }\r
+\r
+  private void setDefaultSerializationFormat(SpringProcessEngineConfiguration configuration) {\r
+    String defaultSerializationFormat = camundaBpmProperties.getDefaultSerializationFormat();\r
+    if (StringUtils.hasText(defaultSerializationFormat)) {\r
+      configuration.setDefaultSerializationFormat(defaultSerializationFormat);\r
+    } else {\r
+      logger.warn("Ignoring invalid defaultSerializationFormat='{}'", defaultSerializationFormat);\r
+    }\r
+  }\r
+\r
+  private void setProcessEngineName(SpringProcessEngineConfiguration configuration) {\r
+    String processEngineName =\r
+        StringUtils.trimAllWhitespace(camundaBpmProperties.getProcessEngineName());\r
+    if (!StringUtils.isEmpty(processEngineName) && !processEngineName.contains("-")) {\r
+      configuration.setProcessEngineName(processEngineName);\r
+    } else {\r
+      logger.warn(\r
+          "Ignoring invalid processEngineName='{}' - must not be null, blank or contain hyphen",\r
+          camundaBpmProperties.getProcessEngineName());\r
+    }\r
+  }\r
+\r
+  private void setJobExecutorAcquireByPriority(SpringProcessEngineConfiguration configuration) {\r
+    Optional.ofNullable(camundaBpmProperties.getJobExecutorAcquireByPriority())\r
+        .ifPresent(configuration::setJobExecutorAcquireByPriority);\r
+  }\r
+\r
+  private void setDefaultNumberOfRetries(SpringProcessEngineConfiguration configuration) {\r
+    Optional.ofNullable(camundaBpmProperties.getDefaultNumberOfRetries())\r
+        .ifPresent(configuration::setDefaultNumberOfRetries);\r
+  }\r
+\r
+  @Bean\r
+  CamundaEventBus camundaEventBus() {\r
+    return new CamundaEventBus();\r
+  }\r
+\r
+  @Bean\r
+  @Qualifier("camunda")\r
+  EventBus eventBus(final CamundaEventBus camundaEventBus) {\r
+    return camundaEventBus.get();\r
+  }\r
+\r
+  @Bean\r
+  ReactorProcessEnginePlugin reactorProcessEnginePlugin(final CamundaEventBus camundaEventBus) {\r
+    return new ReactorProcessEnginePlugin(camundaEventBus);\r
+  }\r
+\r
+  @Bean\r
+  ConnectProcessEnginePlugin connectProcessEnginePlugin() {\r
+    return new ConnectProcessEnginePlugin();\r
+  }\r
+\r
+  @Bean\r
+  SpinProcessEnginePlugin spinProcessEnginePlugin() {\r
+    return new SpinProcessEnginePlugin();\r
+  }\r
+\r
+  @Bean\r
+  ProcessApplicationEventListenerPlugin processApplicationEventListenerPlugin() {\r
+    return new ProcessApplicationEventListenerPlugin();\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/listener/OTFJobExecutorStartingEventListener.java b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/listener/OTFJobExecutorStartingEventListener.java
new file mode 100644 (file)
index 0000000..d2d0194
--- /dev/null
@@ -0,0 +1,44 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.configuration.listener;\r
+\r
+import org.camunda.bpm.spring.boot.starter.event.JobExecutorStartingEventListener;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Value;\r
+\r
+public class OTFJobExecutorStartingEventListener extends JobExecutorStartingEventListener {\r
+\r
+    private static final Logger LOGGER = LoggerFactory.getLogger(OTFJobExecutorStartingEventListener.class);\r
+\r
+    @Value("${otf.camunda.executors-active}")\r
+    private boolean executorsActive;\r
+\r
+    protected void activate() {\r
+        if(!executorsActive){\r
+            LOGGER.info("job executor auto start disabled. otf.camunda.executors-active: " + this.executorsActive);\r
+            jobExecutor.shutdown();\r
+            return;\r
+        }\r
+        if (!jobExecutor.isActive()) {\r
+            jobExecutor.start();\r
+        } else {\r
+            LOGGER.info("job executor is already active");\r
+        }\r
+    }\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/CallTestHeadDelegate.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/CallTestHeadDelegate.java
new file mode 100644 (file)
index 0000000..45511b0
--- /dev/null
@@ -0,0 +1,329 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common;\r
+\r
+import org.oran.otf.camunda.delegate.otf.common.runnable.TestHeadCallable;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.*;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.model.local.TestHeadNode;\r
+import org.oran.otf.common.model.local.TestHeadResult;\r
+import org.oran.otf.common.repository.*;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.common.utility.database.Generic;\r
+import org.oran.otf.common.utility.permissions.PermissionChecker;\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import com.mongodb.client.result.UpdateResult;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.JavaDelegate;\r
+import org.oran.otf.common.model.*;\r
+import org.oran.otf.common.repository.*;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class CallTestHeadDelegate implements JavaDelegate {\r
+  private static final Logger logger = LoggerFactory.getLogger(CallTestHeadDelegate.class);\r
+\r
+  @Autowired\r
+  private UserRepository userRepository;\r
+  @Autowired\r
+  private GroupRepository groupRepository;\r
+  @Autowired\r
+  private WorkflowUtility utility;\r
+  @Autowired\r
+  private TestDefinitionRepository testDefinitionRepository;\r
+  @Autowired\r
+  private TestHeadRepository testHeadRepository;\r
+  @Autowired\r
+  private TestInstanceRepository testInstanceRepository;\r
+  @Autowired\r
+  private MongoTemplate mongoOperation;\r
+\r
+  // Used to retrieve the results from test head runnables.\r
+  List<TestHeadResult> testHeadResults = Collections.synchronizedList(new ArrayList<>());\r
+\r
+  @Override\r
+  public void execute(DelegateExecution execution) throws Exception {\r
+    callTestHead(\r
+        execution.getCurrentActivityId(),\r
+        execution.getProcessDefinitionId(),\r
+        execution.getProcessInstanceId(),\r
+        execution.getProcessBusinessKey(),\r
+        execution.getVariables());\r
+  }\r
+\r
+  public void callTestHead(\r
+      String currentActivityId,\r
+      String processDefinitionId,\r
+      String processInstanceId,\r
+      String processBusinessKey,\r
+      Map<String, Object> variables)\r
+      throws Exception {\r
+    final String logPrefix = Utility.getLoggerPrefix();\r
+    logger.info(logPrefix + "::execute()");\r
+\r
+    // Get vthInput from the Camunda execution variable map.\r
+    List<Map<String, Object>> activityParameters = utility.getVthInput(variables, currentActivityId, logPrefix);\r
+\r
+    // Get the current test execution object.\r
+    TestExecution testExecution = utility.getTestExecution(variables, logPrefix);\r
+\r
+    // Lookup the test head before making computations in the loop, and before calling the runnable.\r
+    // If the lookup is made inside the runnable, concurrent test head calls would bombard the db.\r
+    TestHead testHead = getTestHead(testExecution, currentActivityId, processDefinitionId);\r
+\r
+    WorkflowTask workflowTask = new WorkflowTask(processInstanceId, activityParameters.size(), false);\r
+    ExecutorService pool = workflowTask.getPool();\r
+\r
+    // Try to cast each parameter to a Map, and create runnable tasks.\r
+    for (int i = 0; i < activityParameters.size(); i++) {\r
+      Object oTestHeadParameter = activityParameters.get(i);\r
+      Map<?, ?> mTestHeadParameter;\r
+      try {\r
+        mTestHeadParameter = Utility.toMap(oTestHeadParameter);\r
+        verifyOtfTestHead(mTestHeadParameter, testHead, testExecution, currentActivityId);\r
+      } catch (Exception e) {\r
+        // TODO: Make a design decision to either stop the execution, or attempt to convert the\r
+        // other parameters.\r
+        logger.error(\r
+            String.format(\r
+                "Unable to convert test head parameter at vthInput[%s][%d] to a Map.",\r
+                currentActivityId, i));\r
+        continue;\r
+      }\r
+\r
+      // Get all the arguments for the runnable.\r
+      Object oHeaders = mTestHeadParameter.get("headers"); // optional\r
+      Object oMethod = mTestHeadParameter.get("method"); // required\r
+      Object oPayload = mTestHeadParameter.get("payload"); // optional\r
+      Object oTimeoutInMillis = mTestHeadParameter.get("timeoutInMillis"); // optional\r
+\r
+      // Target typed parameters. Convert all objects to their expected types. Throw exceptions for\r
+      // required parameters, or for parameters that are provided but not of the expected type.\r
+      Map<String, String> headers = new HashMap<>();\r
+      String method = "";\r
+      Map<String, Object> payload = new HashMap<>();\r
+      int timeoutInMillis = 0;\r
+\r
+      if (oHeaders != null) {\r
+        try {\r
+          headers = (Map<String, String>) Utility.toMap(oHeaders);\r
+        } catch (Exception e) {\r
+          logger.error(\r
+              String.format(\r
+                  "Unable to convert test head parameter at vthInput[%s][%d][headers] to a Map.",\r
+                  currentActivityId, i));\r
+        }\r
+      }\r
+\r
+      if (oMethod == null) {\r
+        throw new TestExecutionException(\r
+            String.format(\r
+                "vthInput[%s][%d][method] is a required parameter.", currentActivityId, i));\r
+      } else {\r
+        try {\r
+          method = (String) oMethod;\r
+        } catch (ClassCastException cce) {\r
+          throw new TestExecutionException(\r
+              String.format(\r
+                  "Unable to read vthInput[%s][%d][method] as primitive type String.",\r
+                  processInstanceId, i));\r
+        }\r
+      }\r
+\r
+      if (oPayload != null) {\r
+        try {\r
+          payload = (Map<String, Object>) Utility.toMap(oPayload);\r
+        } catch (Exception e) {\r
+          logger.error(\r
+              String.format(\r
+                  "Unable to convert test head parameter at vthInput[%s][%d][payload] to a Map.",\r
+                  currentActivityId, i));\r
+        }\r
+      }\r
+\r
+      if (oTimeoutInMillis != null) {\r
+        try {\r
+          timeoutInMillis = (int) oTimeoutInMillis;\r
+        } catch (ClassCastException cce) {\r
+          throw new TestExecutionException(\r
+              String.format(\r
+                  "Unable to read vthInput[%s][%d][timeoutInMillis] as primitive type int.",\r
+                  currentActivityId, i));\r
+        }\r
+      }\r
+\r
+//      logger.info("{}(BEFORE) PRINTING THREAD INFORMATION", logPrefix);\r
+//      WorkflowTask.printThreadInformation();\r
+//      logger.info("{}(BEFORE) PRINTING WORKFLOW TASKS", logPrefix);\r
+//      WorkflowTask.printWorkflowTaskResources();\r
+      TestHeadCallable callable =\r
+          new TestHeadCallable(\r
+              timeoutInMillis,\r
+              method,\r
+              headers,\r
+              payload,\r
+              testHead,\r
+              currentActivityId,\r
+              testExecution,\r
+              mongoOperation);\r
+\r
+      // Submit the test head call to the executor service.\r
+      workflowTask.getFutures().add(pool.submit(callable));\r
+    }\r
+\r
+    // Prevent new tasks from being submitted, and allow running tasks to finish.\r
+    pool.shutdown();\r
+\r
+    int numResults = 0;\r
+    while (!pool.isTerminated()) {\r
+      try {\r
+        pool.awaitTermination(1, TimeUnit.SECONDS);\r
+      } catch (InterruptedException e) {\r
+        workflowTask.shutdown(true);\r
+        throw e;\r
+      }\r
+    }\r
+\r
+    workflowTask.shutdown(false);\r
+\r
+//    logger.info("{}(AFTER) PRINTING THREAD INFORMATION", logPrefix);\r
+//    WorkflowTask.printThreadInformation();\r
+//    logger.info("{}(AFTER) PRINTING WORKFLOW TASKS", logPrefix);\r
+//    WorkflowTask.printWorkflowTaskResources();\r
+  }\r
+\r
+  private void saveTestHeadResults(String businessKey) {\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("businessKey").is(businessKey));\r
+    Update update = new Update();\r
+    update.set("testHeadResults", testHeadResults);\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+    // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+    if (result.getMatchedCount() == 0) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "Unable to log the test result because a testExecution associated with businessKey, %s, was not found.",\r
+              businessKey));\r
+    } else if (result.getModifiedCount() == 0) {\r
+      throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+    }\r
+  }\r
+\r
+  private TestHead getTestHead(\r
+      TestExecution testExecution, String currentActivityId, String processDefinitionId) {\r
+    List<BpmnInstance> bpmnInstances = testExecution.getHistoricTestDefinition().getBpmnInstances();\r
+    BpmnInstance bpmnInstance =\r
+        bpmnInstances.stream()\r
+            .filter(\r
+                _bpmnInstance ->\r
+                    _bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(processDefinitionId))\r
+            .findFirst()\r
+            .orElse(null);\r
+\r
+    if (bpmnInstance == null) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "Error looking BpmnInstance with processDefinitionId %s.", processDefinitionId));\r
+    }\r
+\r
+    List<TestHeadNode> testHeads = bpmnInstance.getTestHeads();\r
+    TestHeadNode testHeadNode =\r
+        testHeads.stream()\r
+            .filter(testHead -> testHead.getBpmnVthTaskId().equals(currentActivityId))\r
+            .findAny()\r
+            .orElse(null);\r
+\r
+    if (testHeadNode == null) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "No test head associated with the currentActivityId %s.", currentActivityId));\r
+    }\r
+\r
+    TestHead testHead = Generic.findByIdGeneric(testHeadRepository, testHeadNode.getTestHeadId());\r
+    if (testHead == null) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "The test head with id, %s, was not found in the database.",\r
+              testHeadNode.getTestHeadId()));\r
+    }\r
+    User testExecUser = userRepository.findById(testExecution.getExecutorId().toString()).orElse(null);\r
+    Group testheadGroup =  groupRepository.findById(testHead.getGroupId().toString()).orElse(null);\r
+    if(testExecUser == null){\r
+      throw new TestExecutionException(\r
+              String.format("Can not find user, user id: %s",testExecution.getExecutorId().toString()));\r
+    }\r
+    if(testheadGroup == null){\r
+      throw new TestExecutionException(\r
+              String.format("Can not find test head group, group id: %s",testHead.getGroupId().toString())\r
+      );\r
+    }\r
+\r
+    if( (testHead.isPublic() != null && !testHead.isPublic()) &&\r
+            !PermissionChecker.hasPermissionTo(testExecUser,testheadGroup,UserPermission.Permission.EXECUTE,groupRepository)){\r
+      throw new TestExecutionException(\r
+              String.format(\r
+                      "User(%s) does not have permission to in testHead Group(%s)",\r
+                      testExecUser.get_id().toString(),testheadGroup.get_id().toString()\r
+              ));\r
+    }\r
+    return testHead;\r
+  }\r
+\r
+  private void verifyOtfTestHead(Map activityParams, TestHead testHead, TestExecution execution, String currentActivityId){\r
+    String testHeadName = testHead.getTestHeadName().toLowerCase();\r
+    switch(testHeadName) {\r
+      case "robot":\r
+        try {\r
+          TestInstance testInstance = Generic.findByIdGeneric(testInstanceRepository, execution.getHistoricTestInstance().get_id());\r
+          Map<String, Object> internalTestDataByActivity = (Map<String, Object>) testInstance.getInternalTestData().get(currentActivityId);\r
+          String robotFileId = (String) internalTestDataByActivity.get("robotFileId");\r
+          Map<String, Object> testData = new HashMap<>();\r
+          Map<String, Object> vthInput = new HashMap<>();\r
+          testData.put("robotFileId", robotFileId);\r
+          vthInput.put("testData", testData);\r
+          Map<String, Object> payload = (Map<String, Object>) activityParams.get("payload");\r
+          payload.put("vthInput", vthInput);\r
+        }\r
+        catch (Exception e){\r
+          throw new TestExecutionException(\r
+                  String.format(\r
+                          "Robot test head needs a robot file id: %s.", e.getMessage()));\r
+        }\r
+        break;\r
+      default:\r
+        break;\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/LogTestResultDelegate.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/LogTestResultDelegate.java
new file mode 100644 (file)
index 0000000..0ecb37e
--- /dev/null
@@ -0,0 +1,114 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common;\r
+\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.utility.Utility;\r
+import com.mongodb.client.result.UpdateResult;\r
+\r
+import java.util.Arrays;\r
+import java.util.Date;\r
+import java.util.Map;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.JavaDelegate;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class LogTestResultDelegate implements JavaDelegate {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(LogTestResultDelegate.class);\r
+\r
+  @Autowired\r
+  private TestExecutionRepository testExecutionRepository;\r
+  @Autowired\r
+  private MongoTemplate mongoOperation;\r
+  @Autowired\r
+  private WorkflowUtility utility;\r
+\r
+  @Override\r
+  public void execute(DelegateExecution execution) throws Exception {\r
+    logger.info("[LogTestResult] Starting to log test result.");\r
+    final String logPrefix = Utility.getLoggerPrefix();\r
+    // Get the current test execution object.\r
+    TestExecution testExecution = utility.getTestExecution(execution.getVariables(), logPrefix);\r
+\r
+    // Set the end time right after retrieving the execution. This will not include the save time\r
+    // to the database.\r
+    testExecution.setEndTime(new Date(System.currentTimeMillis()));\r
+\r
+    // Set the processInstanceId because the user may have modified it through a script task.\r
+    testExecution.setProcessInstanceId(execution.getProcessInstanceId());\r
+\r
+    // Get the test result from the execution.\r
+    String testResult = utility.getTestResult(execution.getVariables(), logPrefix).toUpperCase();\r
+    if(testResult.equalsIgnoreCase(ExecutionConstants.TestResult.WORKFLOW_ERROR)){\r
+      testResult = ExecutionConstants.TestResult.ERROR;\r
+    }\r
+    if(Arrays.asList(ExecutionConstants.getAllTestResultStr()).contains(testResult.toUpperCase()))\r
+      testExecution.setTestResult(testResult.toUpperCase());\r
+    else{\r
+      testExecution.setTestResult(ExecutionConstants.TestResult.OTHER);\r
+    }\r
+\r
+    //Get the test result message from the execution\r
+    String testResultMessage = utility.getTestResultMessage(execution.getVariables(), logPrefix);\r
+    testExecution.setTestResultMessage(testResultMessage);\r
+\r
+    // Get test details as a String because it can be saved as one of many "JSON" types. Then try\r
+    // to convert it to a generic map.\r
+    Map<String, Object> testDetails = utility.getTestDetails(execution.getVariables(), logPrefix);\r
+    // Save the converted object to the test execution.\r
+    testExecution.setTestDetails(testDetails);\r
+\r
+\r
+    // Update the Test Execution object to save the result. Find the existing test execution by the\r
+    // processBusinessKey from the delegate execution because it is saved to the database before the\r
+    // user can modify the value.\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("businessKey").is(execution.getProcessBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testResult", testExecution.getTestResult());\r
+    update.set("testResultMessage", testExecution.getTestResultMessage());\r
+    update.set("testDetails", testExecution.getTestDetails());\r
+    update.set("endTime", testExecution.getEndTime());\r
+    update.set("processInstanceId", execution.getProcessInstanceId());\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+    // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+    if (result.getMatchedCount() == 0) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "Unable to log the test result because a testExecution associated with businessKey, %s, was not found.",\r
+              execution.getProcessBusinessKey()));\r
+    } else if (result.getModifiedCount() == 0) {\r
+      throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+    } else {\r
+      logger.info(\r
+          logPrefix + execution.getProcessInstanceId() + ": Saved test result to the database.");\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/PostResultsToDMaaPDelegate.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/PostResultsToDMaaPDelegate.java
new file mode 100644 (file)
index 0000000..41b9d8a
--- /dev/null
@@ -0,0 +1,159 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common;\r
+\r
+import org.oran.otf.cadi.configuration.FilterCondition;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.local.DMaaPRequest;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import org.oran.otf.common.utility.http.RequestUtility;\r
+import com.fasterxml.jackson.core.type.TypeReference;\r
+\r
+import java.util.Base64;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import javax.ws.rs.core.MediaType;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.util.EntityUtils;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.JavaDelegate;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.context.annotation.Conditional;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class PostResultsToDMaaPDelegate implements JavaDelegate {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(PostResultsToDMaaPDelegate.class);\r
+\r
+  @Value("${otf.cadi.aaf-mech-id}")\r
+  private String AAF_APPID;\r
+\r
+  @Value("${otf.cadi.aaf-mech-password}")\r
+  private String AAF_APPPASS;\r
+\r
+  @Value("${otf.environment}")\r
+  private String env;\r
+\r
+  @Autowired private WorkflowUtility utility;\r
+\r
+  private final String template = "https://<hostname>:3905/events/<topic>";\r
+\r
+  @Override\r
+  public void execute(DelegateExecution execution) throws Exception {\r
+    logger.info("[PostResultsToDMaaP] Starting to post test results to dmaap.");\r
+    final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+    // Get the current test execution object.\r
+    TestExecution testExecution = utility.getTestExecution(execution.getVariables(), logPrefix);\r
+\r
+    List<Object> testDataActivity = null;\r
+    Object dataByActivity =\r
+        utility.getTestDataByActivity(\r
+            execution.getVariables(), execution.getCurrentActivityId(), logPrefix);\r
+    if (!(dataByActivity instanceof List)) {\r
+      logger.error(\r
+          execution.getActivityInstanceId()\r
+              + ": Failed to retrieve dmaap requests in test data as list");\r
+      throw new TestExecutionException(\r
+          execution.getActivityInstanceId()\r
+              + ": Missing data to post to dmaap. Failed to retrieve dmaap requests in test data as list");\r
+    }\r
+\r
+    // convert data to map and grab dmaaprequest array\r
+    testDataActivity = (List) dataByActivity;\r
+    List<DMaaPRequest> dmaapRequests = null;\r
+    try {\r
+      dmaapRequests =\r
+          Convert.listToObjectList(testDataActivity, new TypeReference<List<DMaaPRequest>>() {});\r
+    } catch (Exception e) {\r
+      logger.error(\r
+          execution.getActivityInstanceId() + ": Failed to get dmaap requests from test data");\r
+      throw new TestExecutionException(\r
+          execution.getActivityInstanceId() + ": Missing data to post to dmaap. " + e.getMessage(),\r
+          e);\r
+    }\r
+    if (dmaapRequests == null || dmaapRequests.isEmpty()) {\r
+      logger.error(execution.getActivityInstanceId() + ": Failed to retrieve dmaap request list");\r
+      throw new TestExecutionException(\r
+          execution.getActivityInstanceId() + ": Missing dmaap request list");\r
+    }\r
+\r
+    // Get the testDetails object\r
+    Map<String, Object> testDetails = utility.getTestDetails(execution.getVariables(), logPrefix);\r
+\r
+    // Post results to Dmaap\r
+    Map<String, Object> results = postResultsToDmaap(testExecution, dmaapRequests, logPrefix);\r
+\r
+    // Set test details to show results of each post to dmaap\r
+    testDetails.put(execution.getCurrentActivityId(), results);\r
+    execution.setVariable(ExecutionConstants.ExecutionVariable.TEST_DETAILS, testDetails);\r
+    logger.info("[PostResultsToDMaaP] Finished posting test results to dmaap.");\r
+  }\r
+\r
+  private Map<String, Object> postResultsToDmaap(\r
+      TestExecution execution, List<DMaaPRequest> dmaapRequests, String logPrefix) {\r
+    String payload = execution.toString();\r
+    Map<String, Object> results = new HashMap<>();\r
+    Map<String, String> headers = new HashMap<>();\r
+    headers.put("Authorization", getAuthorizationHeader());\r
+    headers.put("Content-Type", MediaType.APPLICATION_JSON);\r
+\r
+    for (DMaaPRequest request : dmaapRequests) {\r
+      String url = new String(template);\r
+      url = url.replace("<hostname>", request.getHostname());\r
+      url = url.replace("<topic>", request.getAsyncTopic());\r
+\r
+      try {\r
+        results.put(url, getResponse(url, payload, headers, request.getRequiresProxy()));\r
+      } catch (Exception e) {\r
+        e.printStackTrace();\r
+        logger.info(logPrefix + "Error while posting to dmaap : " + e.getMessage());\r
+        results.put(url, e.getMessage());\r
+      }\r
+    }\r
+    return results;\r
+  }\r
+\r
+  private Map<String, Object> getResponse(\r
+      String url, String payload, Map<String, String> headers, boolean proxy)\r
+      throws Exception {\r
+    HttpResponse response = RequestUtility.postSync(url, payload, headers, proxy);\r
+    String sRes = EntityUtils.toString(response.getEntity());\r
+    Map<String, Object> res;\r
+    try {\r
+      res = Convert.jsonToMap(sRes);\r
+    } catch (Exception e) {\r
+      res = new HashMap<>();\r
+      res.put("response", sRes);\r
+    }\r
+    return res;\r
+  }\r
+\r
+  private String getAuthorizationHeader() {\r
+    return "Basic "\r
+        + new String(Base64.getEncoder().encode((AAF_APPID + ":" + AAF_APPPASS).getBytes()));\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/RunTestInstanceDelegate.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/RunTestInstanceDelegate.java
new file mode 100644 (file)
index 0000000..7b90e7e
--- /dev/null
@@ -0,0 +1,180 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common;\r
+\r
+import org.oran.otf.camunda.delegate.otf.common.runnable.SynchronousTestInstanceCallable;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.workflow.WorkflowProcessor;\r
+import org.oran.otf.camunda.workflow.WorkflowRequest;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.utility.Utility;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.TimeUnit;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.JavaDelegate;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class RunTestInstanceDelegate implements JavaDelegate {\r
+\r
+  private final String logPrefix = Utility.getLoggerPrefix();\r
+  private final Logger logger = LoggerFactory.getLogger(RunTestInstanceDelegate.class);\r
+  // Used to retrieve the results from test head runnables.\r
+  private final List<TestExecution> testExecutions =\r
+      Collections.synchronizedList(new ArrayList<>());\r
+\r
+  private @Autowired\r
+  WorkflowUtility utility;\r
+  private @Autowired\r
+  TestExecutionRepository testExecutionRepository;\r
+  private @Autowired\r
+  WorkflowProcessor processor;\r
+  private @Autowired MongoTemplate mongoOperation;\r
+\r
+  @Override\r
+  public void execute(DelegateExecution execution) throws Exception {\r
+    runTestInstance(\r
+        execution.getCurrentActivityId(),\r
+        execution.getProcessInstanceId(),\r
+        execution.getVariables());\r
+  }\r
+\r
+  public void runTestInstance(\r
+      String currentActivityId, String processInstanceId, Map<String, Object> variables)\r
+      throws Exception {\r
+    @SuppressWarnings("unchecked")\r
+\r
+    // Get the current test execution object to pass as an argument to the callable, and for data\r
+    // stored in the historicTestInstance\r
+    TestExecution testExecution = utility.getTestExecution(variables, logPrefix);\r
+\r
+    // Get the parallel flow input\r
+    Map<String, ParallelFlowInput> pfloInput =\r
+        (Map<String, ParallelFlowInput>) variables.get("pfloInput");\r
+\r
+    if (!pfloInput.containsKey(currentActivityId)) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "%sCould not find activityId %s in pfloInput.", logPrefix, currentActivityId));\r
+    }\r
+\r
+    ParallelFlowInput parallelFlowInput = pfloInput.get(currentActivityId);\r
+    List<WorkflowRequest> args = parallelFlowInput.getArgs();\r
+    boolean interruptOnFailure = parallelFlowInput.isInterruptOnFailure();\r
+    int maxFailures = parallelFlowInput.getMaxFailures();\r
+    int threadPoolSize = parallelFlowInput.getThreadPoolSize();\r
+\r
+    WorkflowTask workflowTask =\r
+        new WorkflowTask(processInstanceId, threadPoolSize, interruptOnFailure);\r
+    ExecutorService pool = workflowTask.getPool();\r
+\r
+//    logger.info("{}(BEFORE) PRINTING THREAD INFORMATION", logPrefix);\r
+//    WorkflowTask.printThreadInformation();\r
+//    logger.info("{}(BEFORE) PRINTING WORKFLOW TASKS", logPrefix);\r
+//    WorkflowTask.printWorkflowTaskResources();\r
+\r
+    for (WorkflowRequest request : args) {\r
+      request.setExecutorId(testExecution.getExecutorId());\r
+      // If an inner workflow calls the parent workflow, there is a cyclic dependency. To prevent\r
+      // infinite test instances from being spawned, we need to check for cycles. This is only a top\r
+      // level check, but a more thorough check needs to be implemented after 1906.\r
+      if (request.getTestInstanceId() == testExecution.getHistoricTestInstance().get_id()) {\r
+        // Prevent new tasks from being submitted\r
+        pool.shutdown();\r
+        // Shutdown the thread pool, and cleanup threads.\r
+        workflowTask.shutdown(true);\r
+        break;\r
+      }\r
+\r
+      SynchronousTestInstanceCallable synchronousTestInstanceCallable =\r
+          new SynchronousTestInstanceCallable(\r
+              request, testExecution, testExecutionRepository, processor, mongoOperation);\r
+      workflowTask.getFutures().add(pool.submit(synchronousTestInstanceCallable));\r
+    }\r
+\r
+    // Prevent new tasks from being submitted, and allow running tasks to finish.\r
+    pool.shutdown();\r
+\r
+    // Wait for test instances to finish execution, and check for failures.\r
+    while (!pool.isTerminated()) {\r
+      try {\r
+        // Terminate tasks if the test execution failure limit is reached.\r
+        int numFailures;\r
+        synchronized (testExecution) {\r
+          numFailures = getNumberOfFailures(testExecution.getTestInstanceResults());\r
+        }\r
+\r
+        if (numFailures > maxFailures) {\r
+          logger.error(\r
+              String.format(\r
+                  "[PARENT-%s] Shutting down workflow - numFailures: %s, maxFailures: %s.",\r
+                  processInstanceId, numFailures, maxFailures));\r
+          workflowTask.shutdown();\r
+        }\r
+\r
+        pool.awaitTermination(1, TimeUnit.SECONDS);\r
+      } catch (InterruptedException e) {\r
+        throw e;\r
+      }\r
+    }\r
+\r
+    workflowTask.shutdown(false);\r
+\r
+//    logger.info("{}(AFTER) PRINTING THREAD INFORMATION", logPrefix);\r
+//    WorkflowTask.printThreadInformation();\r
+//    logger.info("{}(AFTER) PRINTING WORKFLOW TASKS", logPrefix);\r
+//    WorkflowTask.printWorkflowTaskResources();\r
+  }\r
+\r
+  // Gets the total number of testExecutions that have failed.\r
+  private int getNumberOfFailures(List<TestExecution> testExecutions) {\r
+    int numFailures = 0;\r
+\r
+    for (TestExecution testExecution : testExecutions) {\r
+      if (isTestFailed(testExecution)) {\r
+        numFailures++;\r
+      }\r
+    }\r
+\r
+    return numFailures;\r
+  }\r
+\r
+  // Checks if the testResult is marked as FAILED or FAILURE.\r
+  private boolean isTestFailed(TestExecution testExecution) {\r
+    String testResult = testExecution.getTestResult();\r
+    logger.debug(\r
+        String.format(\r
+            "[PARENT-%s] Test result for inner execution: %s.",\r
+            testExecution.getProcessInstanceId(), testExecution.getTestResult()));\r
+    return testResult.equalsIgnoreCase(ExecutionConstants.TestResult.FAILED)\r
+//        || testResult.equalsIgnoreCase(TestResult.FAILED)\r
+        || testResult.equalsIgnoreCase(ExecutionConstants.TestResult.TERMINATED);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/SendMailDelegate.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/SendMailDelegate.java
new file mode 100644 (file)
index 0000000..9755214
--- /dev/null
@@ -0,0 +1,170 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common;\r
+\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.utility.Utility;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import javax.mail.Message;\r
+import javax.mail.MessagingException;\r
+import javax.mail.Session;\r
+import javax.mail.Transport;\r
+import javax.mail.internet.InternetAddress;\r
+import javax.mail.internet.MimeMessage;\r
+import org.assertj.core.util.Strings;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.JavaDelegate;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class SendMailDelegate implements JavaDelegate {\r
+\r
+  @Autowired private WorkflowUtility utility;\r
+\r
+  private static final String logPrefix = Utility.getLoggerPrefix();\r
+  private static final Logger log = LoggerFactory.getLogger(SendMailDelegate.class);\r
+\r
+  @Override\r
+  public void execute(DelegateExecution execution) throws Exception {\r
+    Map<String, Object> variables = execution.getVariables();\r
+    Map<String, Object> testData = utility.getTestData(variables, logPrefix);\r
+\r
+    Map<String, Object> sendMailData = null;\r
+    try {\r
+      sendMailData =\r
+          (Map<String, Object>) testData.getOrDefault(execution.getCurrentActivityId(), null);\r
+    } catch (Exception e) {\r
+      log.error(e.getMessage());\r
+      throw new TestExecutionException(e);\r
+    }\r
+\r
+    if (sendMailData == null) {\r
+      String err =\r
+          String.format(\r
+              "%sMissing parameters for activityId, %s.",\r
+              logPrefix, execution.getCurrentActivityId());\r
+      log.error(err);\r
+      throw new TestExecutionException(err);\r
+    }\r
+\r
+    // Get the recipient(s)\r
+    Object oRecipients = sendMailData.get("to");\r
+    if (oRecipients == null) {\r
+      String err = String.format("%sRecipients array cannot be null or empty.", logPrefix);\r
+      log.error(err);\r
+      throw new TestExecutionException(err);\r
+    }\r
+    List<String> recipients = null;\r
+    try {\r
+      recipients = (ArrayList<String>) (oRecipients);\r
+      if (recipients.size() == 0) {\r
+        String err = String.format("%sRecipients array cannot be null or empty.", logPrefix);\r
+        log.error(err);\r
+        throw new TestExecutionException(err);\r
+      }\r
+    } catch (Exception e) {\r
+      throw new TestExecutionException(e);\r
+    }\r
+\r
+    for (String recipient : recipients) {\r
+      if (Strings.isNullOrEmpty(recipient)) {\r
+        String err = String.format("%sRecipient cannot be null or empty.", logPrefix);\r
+        log.error(err);\r
+        throw new TestExecutionException(err);\r
+      }\r
+    }\r
+\r
+    // Get the email subject.\r
+    String subject = (String) sendMailData.get("subject");\r
+    if (Strings.isNullOrEmpty(subject.trim())) {\r
+      String err = String.format("%sSubject cannot be null or empty.", logPrefix);\r
+      log.error(err);\r
+      throw new TestExecutionException(err);\r
+    }\r
+\r
+    // Get the body contents.\r
+    String body = (String) sendMailData.get("body");\r
+    if (Strings.isNullOrEmpty(body.trim())) {\r
+      String err = String.format("%sBody cannot be null or empty.", logPrefix);\r
+      log.error(err);\r
+      throw new TestExecutionException(err);\r
+    }\r
+\r
+    TestExecution testExecution = utility.getTestExecution(variables, logPrefix);\r
+    String sender = testExecution.getHistoricEmail();\r
+    String hTestInstanceId = testExecution.getHistoricTestInstance().get_id().toString();\r
+    String processInstanceId = execution.getProcessInstanceId();\r
+\r
+    sendMail(recipients, subject, body, sender, processInstanceId, hTestInstanceId);\r
+  }\r
+\r
+  public void sendMail(\r
+      List<String> recipients,\r
+      String subject,\r
+      String body,\r
+      String sender,\r
+      String processInstanceId,\r
+      String testInstanceId)\r
+      throws Exception {\r
+    // Get the system properties.\r
+    Properties properties = System.getProperties();\r
+\r
+    // Set the SMTP host.\r
+    properties.setProperty("mail.smtp.host", "localhost");\r
+\r
+    // creating session object to get properties\r
+    Session session = Session.getDefaultInstance(properties);\r
+\r
+    try {\r
+      // MimeMessage object.\r
+      MimeMessage message = new MimeMessage(session);\r
+\r
+      // Set From Field: adding senders email to from field.\r
+      message.setFrom(new InternetAddress("OTF_EMAIL-ALERT@localhost"));\r
+\r
+      // Set Subject: subject of the email\r
+      message.setSubject(subject);\r
+\r
+      // set body of the email.\r
+      StringBuffer sb = new StringBuffer();\r
+      sb.append("************************OTF Alerting System************************");\r
+      sb.append("\n\n");\r
+      sb.append(String.format("This message was sent by %s via the Open Test Framework\n", sender));\r
+      sb.append(String.format("processInstanceId: %s\n", processInstanceId));\r
+      sb.append(String.format("testInstanceId: %s", testInstanceId));\r
+      sb.append("\n\n");\r
+      sb.append("******************************************************************");\r
+      sb.append("\n\n");\r
+      sb.append(body);\r
+\r
+      message.setText(sb.toString());\r
+\r
+      // Send email.\r
+      Transport.send(message);\r
+    } catch (MessagingException mex) {\r
+      mex.printStackTrace();\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/AsynchronousTestInstanceCallable.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/AsynchronousTestInstanceCallable.java
new file mode 100644 (file)
index 0000000..680688c
--- /dev/null
@@ -0,0 +1,199 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common.runnable;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.exception.WorkflowProcessorException;\r
+import org.oran.otf.camunda.service.ProcessEngineAwareService;\r
+import org.oran.otf.camunda.workflow.WorkflowProcessor;\r
+import org.oran.otf.camunda.workflow.WorkflowRequest;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.utility.database.TestExecutionUtility;\r
+import com.mongodb.client.result.UpdateResult;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+import java.util.concurrent.Callable;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+\r
+public class AsynchronousTestInstanceCallable extends ProcessEngineAwareService\r
+    implements Callable<TestExecution> {\r
+\r
+  private static final Logger logger =\r
+      LoggerFactory.getLogger(AsynchronousTestInstanceCallable.class);\r
+  private final TestExecution parentTestExecution;\r
+  private final TestExecutionRepository testExecutionRepository;\r
+  private final WorkflowProcessor processor;\r
+  private final MongoTemplate mongoOperation;\r
+\r
+  private final WorkflowRequest request;\r
+  private String processInstanceId;\r
+\r
+  public AsynchronousTestInstanceCallable(\r
+      WorkflowRequest request,\r
+      TestExecution parentTestExecution,\r
+      TestExecutionRepository testExecutionRepository,\r
+      WorkflowProcessor processor,\r
+      MongoTemplate mongoOperation) {\r
+    this.request = request;\r
+    this.parentTestExecution = parentTestExecution;\r
+\r
+    this.processInstanceId = "";\r
+\r
+    this.testExecutionRepository = testExecutionRepository;\r
+    this.processor = processor;\r
+    this.mongoOperation = mongoOperation;\r
+  }\r
+\r
+  public AsynchronousTestInstanceCallable(\r
+      WorkflowRequest request,\r
+      TestExecutionRepository testExecutionRepository,\r
+      WorkflowProcessor processor,\r
+      MongoTemplate mongoOperation) {\r
+    this.request = request;\r
+    this.parentTestExecution = null;\r
+\r
+    this.processInstanceId = "";\r
+\r
+    this.testExecutionRepository = testExecutionRepository;\r
+    this.processor = processor;\r
+    this.mongoOperation = mongoOperation;\r
+  }\r
+\r
+  @Override\r
+  public TestExecution call() throws WorkflowProcessorException {\r
+    try {\r
+      TestExecution initialTestExecution = processor.processWorkflowRequest(request);\r
+      this.processInstanceId = initialTestExecution.getProcessInstanceId();\r
+\r
+      // Create a timer task that will call the cancellation after the specified time.\r
+      TimerTask abortTestInstanceTask =\r
+          new TimerTask() {\r
+            @Override\r
+            public void run() {\r
+              cancelProcessInstance(processInstanceId);\r
+\r
+              // Find the result after the process instance after it has finished.\r
+              TestExecution testExecution =\r
+                  testExecutionRepository\r
+                      .findFirstByProcessInstanceId(processInstanceId)\r
+                      .orElse(null);\r
+              if (testExecution == null) {\r
+                logger.error(\r
+                    String.format(\r
+                        "Process instance with id %s completed, however, a corresponding test execution was not found in the database.",\r
+                        processInstanceId));\r
+              } else {\r
+                testExecution.setTestResult(ExecutionConstants.TestResult.TERMINATED);\r
+                TestExecutionUtility.saveTestResult(\r
+                    mongoOperation, testExecution, testExecution.getTestResult());\r
+\r
+                // Saves the testExecution to the parent test execution if this belongs to a "sub"\r
+                // test\r
+                // instance call.\r
+                // updated terminated\r
+                saveToParentTestExecution(testExecution);\r
+              }\r
+            }\r
+          };\r
+\r
+      // Start the daemon that waits the max time for a running test instance.\r
+      long maxExecutionTimeInMillis = request.getMaxExecutionTimeInMillis();\r
+      if (maxExecutionTimeInMillis > 0) {\r
+        new Timer(true).schedule(abortTestInstanceTask, maxExecutionTimeInMillis);\r
+      }\r
+\r
+      return initialTestExecution;\r
+    } catch (WorkflowProcessorException e) {\r
+      throw e;\r
+    } catch (Exception e) {\r
+      e.printStackTrace();\r
+      return null;\r
+    }\r
+  }\r
+\r
+  private void saveToParentTestExecution(TestExecution testExecution) {\r
+    if (parentTestExecution == null) {\r
+      return;\r
+    }\r
+\r
+    synchronized (parentTestExecution) {\r
+      // Add the testExecution to the parentTestExecution\r
+      parentTestExecution.getTestInstanceResults().add(testExecution);\r
+      Query query = new Query();\r
+      query.addCriteria(Criteria.where("_id").is(parentTestExecution.get_id()));\r
+      // Also add businessKey as a criteria because the object won't be found if the business key\r
+      // was somehow modified in the workflow.\r
+      query.addCriteria(Criteria.where("businessKey").is(parentTestExecution.getBusinessKey()));\r
+      Update update = new Update();\r
+      update.set("testInstanceResults", parentTestExecution.getTestInstanceResults());\r
+      UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+      // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+      if (result.getMatchedCount() == 0) {\r
+        throw new TestExecutionException(\r
+            String.format(\r
+                "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+                parentTestExecution.get_id(), parentTestExecution.getBusinessKey()));\r
+      } else if (result.getModifiedCount() == 0) {\r
+        throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+      }\r
+    }\r
+  }\r
+\r
+  private boolean isProcessInstanceEnded(String processInstanceId) {\r
+    try {\r
+      RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+      ProcessInstance processInstance =\r
+          runtimeService\r
+              .createProcessInstanceQuery()\r
+              .processInstanceId(processInstanceId)\r
+              .singleResult();\r
+      return processInstance == null || processInstance.isEnded();\r
+    } catch (Exception e) {\r
+      logger.error("Exception :", e);\r
+      return true;\r
+    }\r
+  }\r
+\r
+  private boolean cancelProcessInstance(String processInstanceId) {\r
+    try {\r
+      RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+      runtimeService.deleteProcessInstance(\r
+          processInstanceId, "Triggered by user defined max execution time timeout.");\r
+      ProcessInstance processInstance =\r
+          runtimeService\r
+              .createProcessInstanceQuery()\r
+              .processInstanceId(processInstanceId)\r
+              .singleResult();\r
+      return processInstance == null || processInstance.isEnded();\r
+    } catch (Exception e) {\r
+      logger.error("Exception :", e);\r
+      return true;\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/SynchronousTestInstanceCallable.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/SynchronousTestInstanceCallable.java
new file mode 100644 (file)
index 0000000..cffdc1e
--- /dev/null
@@ -0,0 +1,227 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common.runnable;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.exception.WorkflowProcessorException;\r
+import org.oran.otf.camunda.service.ProcessEngineAwareService;\r
+import org.oran.otf.camunda.workflow.WorkflowProcessor;\r
+import org.oran.otf.camunda.workflow.WorkflowRequest;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.utility.database.TestExecutionUtility;\r
+import com.mongodb.client.result.UpdateResult;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+import java.util.concurrent.Callable;\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+\r
+public class SynchronousTestInstanceCallable extends ProcessEngineAwareService\r
+    implements Callable<TestExecution> {\r
+\r
+  private static final Logger logger =\r
+      LoggerFactory.getLogger(SynchronousTestInstanceCallable.class);\r
+  private final TestExecution parentTestExecution;\r
+  private final TestExecutionRepository testExecutionRepository;\r
+  private final WorkflowProcessor processor;\r
+  private final MongoTemplate mongoOperation;\r
+\r
+  private final WorkflowRequest request;\r
+  private String processInstanceId;\r
+\r
+  public SynchronousTestInstanceCallable(\r
+      WorkflowRequest request,\r
+      TestExecution parentTestExecution,\r
+      TestExecutionRepository testExecutionRepository,\r
+      WorkflowProcessor processor,\r
+      MongoTemplate mongoOperation) {\r
+    this.request = request;\r
+    this.parentTestExecution = parentTestExecution;\r
+\r
+    this.processInstanceId = "";\r
+\r
+    this.testExecutionRepository = testExecutionRepository;\r
+    this.processor = processor;\r
+    this.mongoOperation = mongoOperation;\r
+  }\r
+\r
+  public SynchronousTestInstanceCallable(\r
+      WorkflowRequest request,\r
+      TestExecutionRepository testExecutionRepository,\r
+      WorkflowProcessor processor,\r
+      MongoTemplate mongoOperation) {\r
+    this.request = request;\r
+    this.parentTestExecution = null;\r
+\r
+    this.processInstanceId = "";\r
+\r
+    this.testExecutionRepository = testExecutionRepository;\r
+    this.processor = processor;\r
+    this.mongoOperation = mongoOperation;\r
+  }\r
+\r
+  @Override\r
+  public TestExecution call() throws WorkflowProcessorException {\r
+    try {\r
+      TestExecution initialTestExecution = processor.processWorkflowRequest(request);\r
+      this.processInstanceId = initialTestExecution.getProcessInstanceId();\r
+      final Map<String, Boolean> abortionStatus = Collections.synchronizedMap(new HashMap<>());\r
+      abortionStatus.put("isAborted", false);\r
+\r
+      // Create a timer task that will call the cancellation after the specified time.\r
+      TimerTask abortTestInstanceTask =\r
+          new TimerTask() {\r
+            @Override\r
+            public void run() {\r
+              cancelProcessInstance(processInstanceId);\r
+              abortionStatus.put("isAborted", true);\r
+            }\r
+          };\r
+\r
+      // Start the daemon that waits the max time for a running test instance.\r
+      long maxExecutionTimeInMillis = request.getMaxExecutionTimeInMillis();\r
+      if (maxExecutionTimeInMillis > 0) {\r
+        new Timer(true).schedule(abortTestInstanceTask, maxExecutionTimeInMillis);\r
+      }\r
+\r
+      while (!isProcessInstanceEnded(processInstanceId)) {\r
+        Thread.sleep(1000);\r
+      }\r
+\r
+      // Find the result after the process instance after it has finished.\r
+      TestExecution testExecution =\r
+          testExecutionRepository.findFirstByProcessInstanceId(processInstanceId).orElse(null);\r
+      if (testExecution == null) {\r
+        logger.error(\r
+            String.format(\r
+                "Process instance with id %s completed, however, a corresponding test execution was not found in the database.",\r
+                processInstanceId));\r
+      } else {\r
+        // If the test result was not set in the workflow, set it to completed now that we know the\r
+        // process instance has finished executing.\r
+        String testResult = testExecution.getTestResult();\r
+        if (testResult.equalsIgnoreCase("UNKNOWN") || testResult.equalsIgnoreCase("STARTED")) {\r
+          if (abortionStatus.get("isAborted")) {\r
+            testExecution.setTestResult(ExecutionConstants.TestResult.TERMINATED);\r
+          } else {\r
+            testExecution.setTestResult(ExecutionConstants.TestResult.COMPLETED);\r
+          }\r
+\r
+          //TODO: RG remove prints\r
+          System.out.println(testExecution.getTestHeadResults());\r
+          System.out.println(request);\r
+          TestExecutionUtility.saveTestResult(\r
+              mongoOperation, testExecution, testExecution.getTestResult());\r
+        }\r
+\r
+        // Saves the testExecution to the parent test execution if this belongs to a "sub" test\r
+        // instance call.\r
+        saveToParentTestExecution(testExecution);\r
+      }\r
+\r
+      return testExecution;\r
+    } catch (WorkflowProcessorException e) {\r
+      throw e;\r
+    } catch (Exception e) {\r
+      e.printStackTrace();\r
+      return null;\r
+    }\r
+  }\r
+\r
+  private void saveToParentTestExecution(TestExecution testExecution) {\r
+    if (parentTestExecution == null) {\r
+      return;\r
+    }\r
+\r
+    synchronized (parentTestExecution) {\r
+      // Add the testExecution to the parentTestExecution\r
+      parentTestExecution.getTestInstanceResults().add(testExecution);\r
+      Query query = new Query();\r
+      query.addCriteria(Criteria.where("_id").is(parentTestExecution.get_id()));\r
+      // Also add businessKey as a criteria because the object won't be found if the business key\r
+      // was somehow modified in the workflow.\r
+      query.addCriteria(Criteria.where("businessKey").is(parentTestExecution.getBusinessKey()));\r
+      Update update = new Update();\r
+      update.set("testInstanceResults", parentTestExecution.getTestInstanceResults());\r
+      UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+      // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+      if (result.getMatchedCount() == 0) {\r
+        throw new TestExecutionException(\r
+            String.format(\r
+                "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+                parentTestExecution.get_id(), parentTestExecution.getBusinessKey()));\r
+      } else if (result.getModifiedCount() == 0) {\r
+        throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+      }\r
+    }\r
+    logger.info(\r
+        String.format(\r
+            "\t[Child-%s] finished saving result to parentTestExecution with result %s.",\r
+            processInstanceId, testExecution.getTestResult()));\r
+  }\r
+\r
+  private boolean isProcessInstanceEnded(String processInstanceId) {\r
+    try {\r
+      RuntimeService runtimeService =\r
+          BpmPlatform.getProcessEngineService()\r
+              .getProcessEngine(OtfCamundaConfiguration.processEngineName)\r
+              .getRuntimeService();\r
+      ProcessInstance processInstance =\r
+          runtimeService\r
+              .createProcessInstanceQuery()\r
+              .processInstanceId(processInstanceId)\r
+              .singleResult();\r
+      return processInstance == null || processInstance.isEnded();\r
+    } catch (Exception e) {\r
+      logger.error("Exception :", e);\r
+      return true;\r
+    }\r
+  }\r
+\r
+  private boolean cancelProcessInstance(String processInstanceId) {\r
+    try {\r
+      RuntimeService runtimeService =\r
+          BpmPlatform.getProcessEngineService()\r
+              .getProcessEngine(OtfCamundaConfiguration.processEngineName)\r
+              .getRuntimeService();\r
+      runtimeService.deleteProcessInstance(\r
+          processInstanceId, "Triggered by user defined max execution time timeout.");\r
+      ProcessInstance processInstance =\r
+          runtimeService\r
+              .createProcessInstanceQuery()\r
+              .processInstanceId(processInstanceId)\r
+              .singleResult();\r
+      return processInstance == null || processInstance.isEnded();\r
+    } catch (Exception e) {\r
+      logger.error("Exception :", e);\r
+      return true;\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/TestHeadCallable.java b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/TestHeadCallable.java
new file mode 100644 (file)
index 0000000..d0ee267
--- /dev/null
@@ -0,0 +1,267 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.delegate.otf.common.runnable;\r
+\r
+\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.TestHead;\r
+import org.oran.otf.common.model.local.TestHeadRequest;\r
+import org.oran.otf.common.model.local.TestHeadResult;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import org.oran.otf.common.utility.http.RequestUtility;\r
+import com.google.common.base.Strings;\r
+import com.google.gson.JsonParser;\r
+import com.mongodb.client.result.UpdateResult;\r
+import java.io.IOException;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.concurrent.Callable;\r
+\r
+import org.apache.http.HttpHeaders;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.util.EntityUtils;\r
+import org.oran.otf.common.utility.http.HeadersUtility;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+\r
+// TODO : Create a constructor that does not take a testexecution object as a parameter. This means\r
+// that the result should only be returned, and the call to saveResult should be avoided.\r
+public class TestHeadCallable implements Callable<TestHeadResult> {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(TestHeadCallable.class);\r
+  private final String logPrefix = Utility.getLoggerPrefix();\r
+  private final TestExecution testExecution;\r
+\r
+  private final int timeoutInMillis;\r
+  private final String httpMethod;\r
+  private final Map<String, String> headers;\r
+  private final Map<String, Object> body;\r
+  private final TestHead testHead;\r
+  private final String activityId;\r
+\r
+  private final MongoTemplate mongoOperation;\r
+\r
+  private String url;\r
+  private TestHeadResult result;\r
+  private Date startTime;\r
+  private Date endTime;\r
+\r
+  public TestHeadCallable(\r
+      int timeoutInMillis,\r
+      String httpMethod,\r
+      Map<String, String> headers,\r
+      Map<String, Object> vthInput,\r
+      TestHead testHead,\r
+      String activityId,\r
+      TestExecution testExecution,\r
+      MongoTemplate mongoOperation) {\r
+    this.timeoutInMillis = timeoutInMillis;\r
+    this.httpMethod = httpMethod;\r
+    this.headers = headers;\r
+    this.body = vthInput;\r
+    this.testHead = testHead;\r
+    this.activityId = activityId;\r
+    this.testExecution = testExecution;\r
+\r
+    this.mongoOperation = mongoOperation;\r
+\r
+    // Generate the url after the test head is set.\r
+    this.url = generateUrl();\r
+  }\r
+\r
+  @Override\r
+  public TestHeadResult call() throws Exception {\r
+    // If simulation mode is set, then send back expected result after expected delay\r
+    if (testExecution.getHistoricTestInstance().isSimulationMode()) {\r
+      logger.info(logPrefix + "Start call to test head in simulation mode.");\r
+      startTime = new Date(System.currentTimeMillis());\r
+      Map<String, Object> response =\r
+          simulateVTH(\r
+              this.activityId, testExecution.getHistoricTestInstance().getSimulationVthInput());\r
+      endTime = new Date(System.currentTimeMillis());\r
+      logger.info(logPrefix + "Finished call to test head in simulation mode.");\r
+\r
+      //TODO: This will need to change if enhancement is made to allow status codes\r
+      TestHeadResult result = generateResult(response);\r
+      testExecution.getTestHeadResults().add(result);\r
+      saveResult(testExecution);\r
+      return result;\r
+    }\r
+    logger.info(logPrefix + "Start call to test head.");\r
+    HttpResponse response = null;\r
+    TestHeadResult result = null;\r
+    // Set the start time right before the request.\r
+    startTime = new Date(System.currentTimeMillis());\r
+\r
+    // add api key to headers if required\r
+    setApiKeyIfEnabled();\r
+\r
+    //TODO RG Added to slow Execution\r
+    //Thread.sleep(60000);\r
+\r
+    try {\r
+      switch (httpMethod.toLowerCase()) {\r
+        case "post":\r
+          response =\r
+              timeoutInMillis > 0\r
+                  ? RequestUtility.postSync(\r
+                      url, Convert.mapToJson(body), headers, timeoutInMillis, false)\r
+                  : RequestUtility.postSync(url, Convert.mapToJson(body), headers, false);\r
+          break;\r
+        case "get":\r
+          response =\r
+              timeoutInMillis > 0\r
+                  ? RequestUtility.getSync(url, headers, timeoutInMillis, false)\r
+                  : RequestUtility.getSync(url, headers, false);\r
+          break;\r
+        default:\r
+          throw new RuntimeException();\r
+      }\r
+      // Set the end time when the request returns.\r
+      endTime = new Date(System.currentTimeMillis());\r
+      logger.info(logPrefix + "Finished call to test head.");\r
+\r
+      // Generate and return the result.\r
+      result = generateResult(response);\r
+    } catch (Exception e) {\r
+      Map<String, Object> error = new HashMap<>();\r
+      error.put("error", e.getMessage());\r
+      result = generateFailedResult(error);\r
+\r
+      logger.info(logPrefix + "There was an error calling the test head.");\r
+    }\r
+\r
+    testExecution.getTestHeadResults().add(result);\r
+    saveResult(testExecution);\r
+    return result;\r
+  }\r
+\r
+  private void setApiKeyIfEnabled(){\r
+    if(this.testHead.getAuthorizationEnabled() != null && this.testHead.getAuthorizationEnabled().booleanValue()){\r
+      this.headers.put(HttpHeaders.AUTHORIZATION, testHead.getAuthorizationType() + " " + testHead.getAuthorizationCredential());\r
+    }\r
+  }\r
+\r
+  private String generateUrl() {\r
+    String resource = testHead.getResourcePath();\r
+    // Prepend a forward-slash if the resource path exists, and does NOT already start with one. The\r
+    // order of this condition is relevant for null-checks.\r
+    if (!Strings.isNullOrEmpty(resource) && !resource.startsWith("/")) {\r
+      resource = "/" + resource;\r
+    }\r
+    return testHead.getHostname() + ":" + testHead.getPort() + resource;\r
+  }\r
+\r
+  private TestHeadResult generateFailedResult(Map<String, Object> error) {\r
+    int statusCodeError = -1;\r
+    TestHeadRequest requestContent = new TestHeadRequest(HeadersUtility.maskAuth(headers), body);\r
+\r
+    return new TestHeadResult(\r
+        testHead.get_id(), testHead.getTestHeadName(), testHead.getGroupId(), activityId, statusCodeError, requestContent, error, startTime, endTime);\r
+  }\r
+\r
+  private TestHeadResult generateResult(HttpResponse response) throws IOException {\r
+    String sRes = EntityUtils.toString(response.getEntity());\r
+    JsonParser parser = new JsonParser();\r
+    Map<String, Object> res;\r
+    try {\r
+      res = Convert.jsonToMap(sRes);\r
+    } catch (Exception e) {\r
+      res = new HashMap<>();\r
+      res.put("response", sRes);\r
+    }\r
+\r
+    TestHeadRequest requestContent = new TestHeadRequest(HeadersUtility.maskAuth(headers), body);\r
+\r
+    return new TestHeadResult(\r
+        testHead.get_id(), testHead.getTestHeadName(), testHead.getGroupId(), activityId, response.getStatusLine().getStatusCode(), requestContent, res, startTime, endTime);\r
+  }\r
+\r
+  private TestHeadResult generateResult(Map<String, Object> res) {\r
+\r
+    //TODO: This will need to change if enhancement is made to allow status codes\r
+    TestHeadRequest requestContent = new TestHeadRequest(HeadersUtility.maskAuth(headers), body);\r
+\r
+    return new TestHeadResult(\r
+        testHead.get_id(), testHead.getTestHeadName(), testHead.getGroupId(), activityId, 200, requestContent, res, startTime, endTime);\r
+  }\r
+\r
+  private void saveResult(TestExecution testExecution) {\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));\r
+    // Also add businessKey as a criteria because the object won't be found if the business key\r
+    // was somehow modified in the workflow.\r
+    query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testHeadResults", testExecution.getTestHeadResults());\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+    // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+    if (result.getMatchedCount() == 0) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+              testExecution.get_id(), testExecution.getBusinessKey()));\r
+    } else if (result.getModifiedCount() == 0) {\r
+      throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+    }\r
+  }\r
+\r
+  private Map<String, Object> simulateVTH(String activityId, Map<String, Object> simulationVth) {\r
+    int delay = 0;\r
+    Map response = new HashMap<String, Object>();\r
+    if (simulationVth.containsKey(activityId)) {\r
+      Object obj = simulationVth.get(activityId);\r
+      if (obj instanceof Map) {\r
+        simulationVth = (Map) obj;\r
+      }\r
+    } else {\r
+      return null;\r
+    }\r
+\r
+    if (simulationVth.containsKey("delay")) {\r
+      Object obj = simulationVth.get("delay");\r
+      if (obj instanceof Integer) {\r
+        delay = (int) obj;\r
+      }\r
+    }\r
+\r
+    if (simulationVth.containsKey("response")) {\r
+      Object obj = simulationVth.get("response");\r
+      if (obj instanceof Map) {\r
+        response = (Map) obj;\r
+      }\r
+    }\r
+    logger.info(logPrefix + "Starting simulated call to test head.");\r
+\r
+    try {\r
+      Thread.sleep(delay);\r
+    } catch (InterruptedException e) {\r
+      e.printStackTrace();\r
+      logger.info(logPrefix + "Error simulating call to test head.");\r
+      return null;\r
+    }\r
+    logger.info(logPrefix + "Finished simulating call to test head.");\r
+    return response;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/exception/TestExecutionException.java b/otf-camunda/src/main/java/org/oran/otf/camunda/exception/TestExecutionException.java
new file mode 100644 (file)
index 0000000..b5f673d
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.exception;\r
+\r
+public class TestExecutionException extends RuntimeException {\r
+\r
+  public TestExecutionException(String message) {\r
+    super(message);\r
+  }\r
+\r
+  public TestExecutionException(String message, Throwable cause) {\r
+    super(message, cause);\r
+  }\r
+\r
+  public TestExecutionException(Throwable cause) {\r
+    super(cause);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowException.java b/otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowException.java
new file mode 100644 (file)
index 0000000..605c6b0
--- /dev/null
@@ -0,0 +1,95 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.camunda.exception;\r
+\r
+import java.io.Serializable;\r
+\r
+/**\r
+ * An object that represents a workflow exception.\r
+ */\r
+public class WorkflowException implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private final String processKey;\r
+  private final int errorCode;\r
+  private final String errorMessage;\r
+  private final String workStep;\r
+\r
+  /**\r
+   * Constructor\r
+   *\r
+   * @param processKey the process key for the process that generated the exception\r
+   * @param errorCode the numeric error code (normally 1xxx or greater)\r
+   * @param errorMessage a short error message\r
+   */\r
+  public WorkflowException(String processKey, int errorCode, String errorMessage) {\r
+    this.processKey = processKey;\r
+    this.errorCode = errorCode;\r
+    this.errorMessage = errorMessage;\r
+    workStep = "*";\r
+  }\r
+\r
+  public WorkflowException(String processKey, int errorCode, String errorMessage, String workStep) {\r
+    this.processKey = processKey;\r
+    this.errorCode = errorCode;\r
+    this.errorMessage = errorMessage;\r
+    this.workStep = workStep;\r
+  }\r
+\r
+  /**\r
+   * Returns the process key.\r
+   */\r
+  public String getProcessKey() {\r
+    return processKey;\r
+  }\r
+\r
+  /**\r
+   * Returns the error code.\r
+   */\r
+  public int getErrorCode() {\r
+    return errorCode;\r
+  }\r
+\r
+  /**\r
+   * Returns the error message.\r
+   */\r
+  public String getErrorMessage() {\r
+    return errorMessage;\r
+  }\r
+\r
+  /**\r
+   * Returns the error message.\r
+   */\r
+  public String getWorkStep() {\r
+    return workStep;\r
+  }\r
+\r
+  /**\r
+   * Returns a string representation of this object.\r
+   */\r
+  @Override\r
+  public String toString() {\r
+    return getClass().getSimpleName() + "[processKey=" + getProcessKey() + ",errorCode="\r
+        + getErrorCode()\r
+        + ",errorMessage=" + getErrorMessage() + ",workStep=" + getWorkStep() + "]";\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowProcessorException.java b/otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowProcessorException.java
new file mode 100644 (file)
index 0000000..8997656
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.exception;\r
+\r
+import org.oran.otf.camunda.model.WorkflowResponse;\r
+\r
+public class WorkflowProcessorException extends RuntimeException {\r
+\r
+  private WorkflowResponse response;\r
+\r
+  public WorkflowProcessorException(WorkflowResponse response) {\r
+    this.response = response;\r
+  }\r
+\r
+  public WorkflowResponse getWorkflowResponse() {\r
+    return response;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/listener/EndEventListener.java b/otf-camunda/src/main/java/org/oran/otf/camunda/listener/EndEventListener.java
new file mode 100644 (file)
index 0000000..f886000
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.listener;\r
+\r
+import org.oran.otf.event.TestInstanceCompletionEvent;\r
+import com.google.gson.JsonObject;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.ExecutionListener;\r
+import org.camunda.bpm.extension.reactor.bus.CamundaSelector;\r
+import org.camunda.bpm.extension.reactor.spring.listener.ReactorExecutionListener;\r
+import org.camunda.bpm.model.bpmn.instance.EndEvent;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.context.ApplicationEventPublisher;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+@CamundaSelector(event = ExecutionListener.EVENTNAME_END)\r
+public class EndEventListener extends ReactorExecutionListener {\r
+\r
+  private static Logger LOGGER = LoggerFactory.getLogger(EndEventListener.class);\r
+\r
+  @Autowired\r
+  private ApplicationEventPublisher publisher;\r
+\r
+  @Override\r
+  public void notify(DelegateExecution execution) {\r
+    JsonObject jmsg = new JsonObject();\r
+    jmsg.addProperty("executionId", execution.getProcessInstanceId());\r
+    jmsg.addProperty("origin", "otf-camunda");\r
+    if (execution.getBpmnModelElementInstance() instanceof EndEvent) {\r
+      LOGGER.info(execution.getProcessInstanceId() + " is finished.");\r
+      jmsg.addProperty("status", "completed");\r
+      publisher.publishEvent(new TestInstanceCompletionEvent(this, jmsg, execution));\r
+    }\r
+  }\r
+\r
+  private void onEndEvent(DelegateExecution execution) {\r
+\r
+  }\r
+\r
+  private void onVthEnd(DelegateExecution execution) {\r
+    // Useful for reporting back the result of a VTH\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/listener/StartEventListener.java b/otf-camunda/src/main/java/org/oran/otf/camunda/listener/StartEventListener.java
new file mode 100644 (file)
index 0000000..9fa6d14
--- /dev/null
@@ -0,0 +1,97 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.listener;\r
+\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.utility.Utility;\r
+import com.google.gson.JsonObject;\r
+import com.mongodb.client.result.UpdateResult;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.ExecutionListener;\r
+import org.camunda.bpm.extension.reactor.bus.CamundaSelector;\r
+import org.camunda.bpm.extension.reactor.spring.listener.ReactorExecutionListener;\r
+import org.camunda.bpm.model.bpmn.instance.StartEvent;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+@CamundaSelector(event = ExecutionListener.EVENTNAME_END)\r
+public class StartEventListener extends ReactorExecutionListener {\r
+\r
+  @Autowired\r
+  WorkflowUtility utility;\r
+\r
+  @Autowired\r
+  MongoTemplate mongoOperation;\r
+\r
+  private static Logger LOGGER = LoggerFactory.getLogger(StartEventListener.class);\r
+\r
+  @Override\r
+  public void notify(DelegateExecution execution) {\r
+    if (execution.getBpmnModelElementInstance() instanceof StartEvent) {\r
+      LOGGER.info(execution.getProcessInstanceId() + " has started.");\r
+      //setTestResult(execution, ExecutionConstants.TestResult.STARTED);\r
+    }\r
+  }\r
+\r
+  private void onStartEvent(DelegateExecution execution) {\r
+  }\r
+\r
+  private void onVthStart(DelegateExecution execution) {\r
+    // Useful for reporting back the exact parameters being sent to the VTH as they can be modified\r
+    // in the workflow\r
+  }\r
+\r
+  private void setTestResult(DelegateExecution execution, String result){\r
+    // Get the current test execution object.\r
+    final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+    TestExecution testExecution =\r
+            utility.getExecutionVariable(\r
+                    execution.getVariables(), ExecutionConstants.ExecutionVariable.TEST_EXECUTION, TestExecution.class);\r
+    // Perform a null-check to ensure it is available. It's critical to throw an exception if it\r
+    // is not available since the object is essential for results.\r
+    if (testExecution == null) {\r
+      LOGGER.error(logPrefix + "Test execution is null.");\r
+      throw new TestExecutionException("The test execution was not found.");\r
+    }\r
+    execution.setVariable(ExecutionConstants.ExecutionVariable.TEST_RESULT, result);\r
+\r
+    testExecution.setTestResult(result);\r
+    testExecution.setProcessInstanceId(execution.getProcessInstanceId());\r
+\r
+\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("businessKey").is(execution.getProcessBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testResult", testExecution.getTestResult());\r
+    update.set("processInstanceId", execution.getProcessInstanceId());\r
+    UpdateResult updateResult = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+\r
+  }\r
+\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/listener/TaskEndEventListener.java b/otf-camunda/src/main/java/org/oran/otf/camunda/listener/TaskEndEventListener.java
new file mode 100644 (file)
index 0000000..37ca685
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.listener;\r
+\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.bpm.engine.delegate.ExecutionListener;\r
+import org.camunda.bpm.engine.impl.context.Context;\r
+import org.camunda.bpm.engine.impl.interceptor.Command;\r
+import org.camunda.bpm.engine.impl.interceptor.CommandContext;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.camunda.bpm.extension.reactor.bus.CamundaSelector;\r
+import org.camunda.bpm.extension.reactor.spring.listener.ReactorExecutionListener;\r
+import org.camunda.bpm.model.bpmn.instance.Task;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+@CamundaSelector(event = ExecutionListener.EVENTNAME_END)\r
+public class TaskEndEventListener extends ReactorExecutionListener {\r
+\r
+    @Autowired\r
+    WorkflowUtility utility;\r
+\r
+    @Autowired\r
+    MongoTemplate mongoOperation;\r
+\r
+    @Autowired\r
+    RuntimeService runtimeService;\r
+\r
+    private static Logger LOGGER = LoggerFactory.getLogger(TaskEndEventListener.class);\r
+\r
+    @Override\r
+    public void notify(DelegateExecution execution) {\r
+        if(execution.getBpmnModelElementInstance() instanceof Task){\r
+            String processInstanceId = execution.getProcessInstanceId();\r
+            ProcessInstance processInstance;\r
+            try {\r
+                processInstance = checkProcessInstanceStatus(processInstanceId);\r
+            }catch(Exception e){\r
+               throw new TestExecutionException("Error trying to obtain process instance status, error: " + e) ;\r
+            }\r
+            // if process instance not found then terminate the current process\r
+            if(processInstance == null || processInstance.isEnded() || processInstance.isSuspended()){\r
+                String logPrefix = Utility.getLoggerPrefix();\r
+\r
+                LOGGER.info(logPrefix + "Process Instance not found. Terminating current job (thread).");\r
+                Thread.currentThread().interrupt();\r
+                throw new TestExecutionException("Terminated Process Instance: " + processInstanceId + ". Process Instance no longer exists, thread has been forcefully interrupted");\r
+            }\r
+        }\r
+    }\r
+\r
+    private ProcessInstance checkProcessInstanceStatus(String processInstanceId){\r
+        return Context.getProcessEngineConfiguration().getCommandExecutorTxRequiresNew().execute(new Command<ProcessInstance>() {\r
+            @Override\r
+            public ProcessInstance execute(CommandContext commandContext){\r
+                return runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();\r
+            }\r
+        });\r
+    }\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/model/ExecutionConstants.java b/otf-camunda/src/main/java/org/oran/otf/camunda/model/ExecutionConstants.java
new file mode 100644 (file)
index 0000000..d266457
--- /dev/null
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.model;\r
+\r
+public class ExecutionConstants {\r
+  public class TestResult {\r
+    public static final String STARTED = "STARTED";\r
+    public static final String COMPLETED = "COMPLETED";\r
+    //remore redundent test results\r
+//    public static final String FAILURE = "FAILURE";\r
+    public static final String FAILED = "FAILED";\r
+    public static final String STOPPED = "STOPPED";\r
+    public static final String SUCCESS = "SUCCESS";\r
+    public static final String TERMINATED = "TERMINATED";\r
+    public static final String UNAUTHORIZED = "UNAUTHORIZED";\r
+    public static final String DOES_NOT_EXIST = "DOES_NOT_EXIST";\r
+    public static final String UNKNOWN = "UNKNOWN";\r
+    // error can be assignned in a workflow. if user uses workflow error reassign to error\r
+    public static final String ERROR = "ERROR";\r
+    // workflow error is only used for exceptions and bugs\r
+    public static final String WORKFLOW_ERROR = "WORKFLOW_ERROR";\r
+    public static final String OTHER = "OTHER";\r
+  }\r
+\r
+  public class ExecutionVariable {\r
+    public static final String TEST_EXECUTION = "otf-execution-testExecution";\r
+    public static final String TEST_EXECUTION_ENCRYPTED = "otf-execution-encrypted";\r
+    public static final String PFLO_INPUT = "pfloInput";\r
+    public static final String TEST_DATA = "testData";\r
+    public static final String TEST_DETAILS = "testDetails";\r
+    public static final String TEST_RESULT = "testResult";\r
+    public static final String VTH_INPUT = "vthInput";\r
+    public static final String TEST_RESULT_MESSAGE = "testResultMessage";\r
+  }\r
+\r
+  public static String [] getAllTestResultStr(){\r
+      return new String[] {TestResult.STARTED,TestResult.COMPLETED,TestResult.FAILED,\r
+              TestResult.STOPPED,TestResult.SUCCESS,TestResult.TERMINATED,\r
+              TestResult.UNAUTHORIZED,TestResult.DOES_NOT_EXIST,TestResult.UNKNOWN,\r
+              TestResult.ERROR,TestResult.OTHER};\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/model/WorkflowResponse.java b/otf-camunda/src/main/java/org/oran/otf/camunda/model/WorkflowResponse.java
new file mode 100644 (file)
index 0000000..ea9fc28
--- /dev/null
@@ -0,0 +1,88 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.model;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+\r
+import java.util.Map;\r
+\r
+/**\r
+ * @version 1.0 Synchronous workflow response bean\r
+ */\r
+public class WorkflowResponse {\r
+\r
+  private String response;\r
+  private String message;\r
+  private String processInstanceId;\r
+  private Map<String, String> variables;\r
+  private TestExecution testExecution;\r
+  private int messageCode;\r
+\r
+  public String getResponse() {\r
+    return response;\r
+  }\r
+\r
+  public void setResponse(String response) {\r
+    this.response = response;\r
+  }\r
+\r
+  public String getMessage() {\r
+    return message;\r
+  }\r
+\r
+  public void setMessage(String message) {\r
+    this.message = message;\r
+  }\r
+\r
+  public String getProcessInstanceId() {\r
+    return processInstanceId;\r
+  }\r
+\r
+  public void setProcessInstanceId(String pID) {\r
+    this.processInstanceId = pID;\r
+  }\r
+\r
+  public Map<String, String> getVariables() {\r
+    return variables;\r
+  }\r
+\r
+  public void setVariables(Map<String, String> variables) {\r
+    this.variables = variables;\r
+  }\r
+\r
+  public int getMessageCode() {\r
+    return messageCode;\r
+  }\r
+\r
+  public void setMessageCode(int messageCode) {\r
+    this.messageCode = messageCode;\r
+  }\r
+\r
+  public TestExecution getTestExecution() {\r
+    return testExecution;\r
+  }\r
+\r
+  public void setTestExecution(TestExecution testExecution) {\r
+    this.testExecution = testExecution;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/plugin/OtfIncidentHandlerPlugin.java b/otf-camunda/src/main/java/org/oran/otf/camunda/plugin/OtfIncidentHandlerPlugin.java
new file mode 100644 (file)
index 0000000..6aceed4
--- /dev/null
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.plugin;\r
+\r
+import org.oran.otf.camunda.workflow.handler.ExternalTaskIncidentHandler;\r
+import org.oran.otf.camunda.workflow.handler.FailedJobIncidentHandler;\r
+import java.util.Arrays;\r
+import org.camunda.bpm.engine.ProcessEngine;\r
+import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;\r
+import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class OtfIncidentHandlerPlugin implements ProcessEnginePlugin {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(OtfIncidentHandlerPlugin.class);\r
+\r
+  @Autowired\r
+  private FailedJobIncidentHandler failedJobIncidentHandler;\r
+  @Autowired\r
+  private ExternalTaskIncidentHandler externalTaskIncidentHandler;\r
+\r
+  @Override\r
+  public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {\r
+    logger.info("Adding Open Test Framework custom incident handlers.");\r
+    processEngineConfiguration.setCustomIncidentHandlers(\r
+        Arrays.asList(failedJobIncidentHandler, externalTaskIncidentHandler));\r
+  }\r
+\r
+  @Override\r
+  public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {\r
+  }\r
+\r
+  @Override\r
+  public void postProcessEngineBuild(ProcessEngine processEngine) {\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/service/CamundaShutdown.java b/otf-camunda/src/main/java/org/oran/otf/camunda/service/CamundaShutdown.java
new file mode 100644 (file)
index 0000000..c414528
--- /dev/null
@@ -0,0 +1,143 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.service;\r
+\r
+import static org.springframework.data.mongodb.core.query.Criteria.where;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.common.model.TestExecution;\r
+\r
+import org.oran.otf.service.impl.DeveloperServiceImpl;\r
+import java.util.ArrayList;\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.OptimisticLockingException;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.BulkOperations;\r
+import org.springframework.data.mongodb.core.BulkOperations.BulkMode;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class CamundaShutdown {\r
+\r
+  private Logger logger = LoggerFactory.getLogger(DeveloperServiceImpl.class);\r
+\r
+  @Autowired\r
+  private MongoTemplate mongoTemplate;\r
+\r
+  public CamundaShutdown(){}\r
+\r
+  //TODO: delete unused code\r
+  public Set<String> gracefulShutdown(){\r
+    Set<String> processIds = new HashSet<>();\r
+\r
+    try {\r
+      if (!WorkflowTask.workflowTasksByExecutionId.isEmpty()) {\r
+        processIds = WorkflowTask.workflowTasksByExecutionId.keySet();\r
+        if (processIds != null) {\r
+          suspendTasks(processIds);\r
+          //1. Update processes running as TERMINATED\r
+          BulkOperations updates = prepareBatchUpdate(processIds);\r
+          updates.execute();\r
+\r
+          //3.kill poolthreads\r
+          processIds = this.shutdownAllProcessThreads(processIds);\r
+          //this.shutdownAllProcessThreads(processIds);\r
+\r
+          //2.look up process instances and delete the suspeded processes\r
+          processIds = queryProcessInstances(processIds);\r
+\r
+        }\r
+      }\r
+    }catch (OptimisticLockingException e){\r
+      //4. Update processes running as TERMINATED\r
+      BulkOperations threadsInterrupted = prepareBatchUpdate(processIds);\r
+      threadsInterrupted.execute();\r
+      logger.info("Optimistic error was caught by graceful shutdown method");\r
+    }\r
+    return processIds;\r
+  }\r
+  private void suspendTasks(Set<String> processIds){\r
+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(\r
+        OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+    for(String id: processIds){\r
+      runtimeService.suspendProcessInstanceById(id);\r
+    }\r
+  }\r
+\r
+    private Set<String> queryProcessInstances(Set<String> processIds){\r
+      RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(\r
+          OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+      for(String id: processIds){\r
+        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();\r
+        if(instance == null || instance.isEnded()){\r
+          processIds.remove(id);\r
+        }\r
+      }\r
+      List<String> del = new ArrayList<>(processIds);\r
+      runtimeService.deleteProcessInstances(del, "Camunda Shutting down, proccess forcefully terminated", false, false , false);\r
+      return processIds;\r
+\r
+    }\r
+\r
+  private Set<String> shutdownAllProcessThreads(Set<String> processIds){\r
+    Set<String> terminatedProcesses = new HashSet<>();\r
+    Iterator processes = processIds.iterator();\r
+    //Iterator processes = WorkflowTask.workflowTasksByExecutionId.entrySet().iterator();\r
+    while(processes.hasNext()){\r
+      Object processHolder = processes.next();\r
+      List<WorkflowTask> tasks = WorkflowTask.workflowTasksByExecutionId.get(processHolder.toString());\r
+      //List<WorkflowTask> tasks = WorkflowTask.workflowTasksByExecutionId.get(processes.next());\r
+      if(tasks != null){\r
+        terminatedProcesses.add(processHolder.toString());\r
+        for(WorkflowTask task: tasks){\r
+          task.shutdown(true);\r
+        }\r
+      }\r
+\r
+      else{\r
+        //processIds.remove(processes.next());\r
+      }\r
+    }\r
+    return terminatedProcesses;\r
+  }\r
+  private BulkOperations prepareBatchUpdate(Set<String> processIds){\r
+    //Set<String> processInstanceIds = this.runningProcessInstanceIds();\r
+    Iterator<String> ids = processIds.iterator();//processInstanceIds.iterator();\r
+    BulkOperations bulkOperations = mongoTemplate.bulkOps(BulkMode.ORDERED, TestExecution.class);\r
+    while(ids.hasNext()){\r
+      ids.hasNext();\r
+      //Get tasks by processInstanceId\r
+      Update update = new Update().set("testResult", TestResult.TERMINATED).set("testResultMessage", "Camunda application had to shutdown for maintenance, Test execution was TERMINATED");\r
+      bulkOperations.updateOne(Query.query(where("processInstanceId").is(ids.next())), update);\r
+    }\r
+    return bulkOperations;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfExternalTaskService.java b/otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfExternalTaskService.java
new file mode 100644 (file)
index 0000000..c23d1cb
--- /dev/null
@@ -0,0 +1,195 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.service;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.delegate.otf.common.CallTestHeadDelegate;\r
+import org.oran.otf.camunda.delegate.otf.common.RunTestInstanceDelegate;\r
+import com.google.common.util.concurrent.ThreadFactoryBuilder;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.concurrent.ThreadFactory;\r
+import java.util.concurrent.ThreadLocalRandom;\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.ExternalTaskService;\r
+import org.camunda.bpm.engine.externaltask.LockedExternalTask;\r
+import org.camunda.bpm.engine.variable.VariableMap;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class OtfExternalTaskService {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(OtfExternalTaskService.class);\r
+  public static boolean isEnabled;\r
+  private static long pollIntervalInMillis = 1000;\r
+  @Autowired CallTestHeadDelegate callTestHeadDelegate;\r
+  @Autowired RunTestInstanceDelegate runTestInstanceDelegate;\r
+  private ExternalTaskService externalTaskService;\r
+\r
+  private List<LockedExternalTask> externalTasks;\r
+\r
+  @Value("${otf.camunda.executors-active}")\r
+  private boolean executorsActive;\r
+\r
+  @EventListener(ApplicationReadyEvent.class)\r
+  public void initialize() {\r
+    this.externalTaskService =\r
+        BpmPlatform.getProcessEngineService()\r
+            .getProcessEngine(OtfCamundaConfiguration.processEngineName)\r
+            .getExternalTaskService();\r
+\r
+    pollIntervalInMillis = ThreadLocalRandom.current().nextLong(500, 5000);\r
+    //    this.externalTaskService =\r
+    //        BpmPlatform.getProcessEngineService()\r
+    //            .getProcessEngine(OtfCamundaConfiguration.processEngineName)\r
+    //            .getExternalTaskService();\r
+\r
+    logger.info(\r
+        "Initializing external task service with poll interval at {}", pollIntervalInMillis);\r
+    externalTasks = new ArrayList<>();\r
+    isEnabled = this.executorsActive;\r
+    logger.info("External Task Worker otf.camunda.executors-active set to : "  + this.executorsActive);\r
+    Thread t =\r
+        new Thread(\r
+            () -> {\r
+              while (true) {\r
+                try {\r
+                  if (isEnabled) {\r
+                    acquire();\r
+                  }\r
+\r
+                  Thread.sleep(pollIntervalInMillis);\r
+                } catch (Exception e) {\r
+                  logger.error(e.getMessage());\r
+                }\r
+              }\r
+            });\r
+\r
+    t.start();\r
+  }\r
+\r
+  private void acquire() {\r
+    externalTasks.clear();\r
+    List<LockedExternalTask> externalTasks =\r
+        externalTaskService\r
+            .fetchAndLock(10, "etw_" + OtfCamundaConfiguration.processEngineName)\r
+            .topic("vth", 43200000)\r
+            .enableCustomObjectDeserialization()\r
+            .topic("testInstance", 43200000)\r
+            .enableCustomObjectDeserialization()\r
+            .execute();\r
+    externalTasks.forEach(this::handleExternalTask);\r
+  }\r
+\r
+  private void handleExternalTask(LockedExternalTask task) {\r
+    logger.info("[" + task.getId() + "]: Handling external task for topic: " + task.getTopicName());\r
+    String topicName = task.getTopicName();\r
+    ExternalTaskCallable callable;\r
+\r
+    // Set retries to 0 for the current task.\r
+    // externalTaskService.setRetries(task.getId(), 0);\r
+\r
+    switch (topicName) {\r
+      case "vth":\r
+        callable = new ExternalTaskCallable(task, OtfExternalTask.VTH);\r
+        break;\r
+      case "testInstance":\r
+        callable = new ExternalTaskCallable(task, OtfExternalTask.TEST_INSTANCE);\r
+        break;\r
+      default:\r
+        String err = String.format("The topic name %s has no external task handler.", topicName);\r
+        logger.error(err);\r
+        externalTaskService.handleFailure(task.getId(), task.getWorkerId(), err, 0, 0);\r
+        return;\r
+    }\r
+\r
+    try {\r
+      ThreadFactory namedThreadFactory =\r
+          new ThreadFactoryBuilder().setNameFormat("etw-" + task.getTopicName() + "-%d").build();\r
+      namedThreadFactory.newThread(callable).start();\r
+    } catch (Exception e) {\r
+      externalTaskService.handleFailure(\r
+          task.getId(), task.getWorkerId(), e.getMessage(), e.toString(), 0, 0);\r
+    }\r
+  }\r
+\r
+  public enum OtfExternalTask {\r
+    VTH,\r
+    TEST_INSTANCE\r
+  }\r
+\r
+  public class ExternalTaskCallable implements Runnable {\r
+\r
+    private final LockedExternalTask task;\r
+    private final OtfExternalTask type;\r
+\r
+    private final String activityId;\r
+    private final String processDefinitionId;\r
+    private final String processInstanceId;\r
+    private final String processBusinessKey;\r
+    private VariableMap variables;\r
+\r
+    private ExternalTaskCallable(LockedExternalTask lockedExternalTask, OtfExternalTask type) {\r
+      this.task = lockedExternalTask;\r
+      this.type = type;\r
+\r
+      this.activityId = task.getActivityId();\r
+      this.processDefinitionId = task.getProcessDefinitionId();\r
+      this.processInstanceId = task.getProcessInstanceId();\r
+      this.processBusinessKey = task.getBusinessKey();\r
+      this.variables = task.getVariables();\r
+    }\r
+\r
+    @Override\r
+    public void run() {\r
+      try {\r
+        if (type == OtfExternalTask.VTH) {\r
+          callTestHeadDelegate.callTestHead(\r
+              activityId, processDefinitionId, processInstanceId, processBusinessKey, variables);\r
+        } else if (type == OtfExternalTask.TEST_INSTANCE) {\r
+          runTestInstanceDelegate.runTestInstance(activityId, processInstanceId, variables);\r
+        } else {\r
+          logger.error(\r
+              String.format(\r
+                  "Could not find the appropriate function for external task with id %s.", type));\r
+        }\r
+      } catch (Exception e) {\r
+        String err = String.format("Encountered error %s", e.getMessage());\r
+        externalTaskService.handleFailure(\r
+            task.getId(), task.getWorkerId(), e.getMessage(), err, 0, 0);\r
+        return;\r
+      }\r
+\r
+      synchronized (externalTaskService) {\r
+        try {\r
+          externalTaskService.complete(task.getId(), task.getWorkerId(), variables);\r
+        } catch (Exception e) {\r
+          String err = String.format("Encountered error %s", e.getMessage());\r
+          e.printStackTrace();\r
+          externalTaskService.handleFailure(\r
+                  task.getId(), task.getWorkerId(), e.getMessage(), err, 0, 0);\r
+        }\r
+      }\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfWorkflowTaskCleanupService.java b/otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfWorkflowTaskCleanupService.java
new file mode 100644 (file)
index 0000000..ca9d7a8
--- /dev/null
@@ -0,0 +1,75 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.service;\r
+\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import java.util.List;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class OtfWorkflowTaskCleanupService {\r
+  @Autowired RuntimeService runtimeService;\r
+  public static boolean isEnabled = false;\r
+\r
+  @EventListener(ApplicationReadyEvent.class)\r
+  public void init() {\r
+    Thread otfCleanupService = new Thread(new Worker());\r
+    otfCleanupService.start();\r
+  }\r
+\r
+  public class Worker implements Runnable {\r
+    @Override\r
+    public void run() {\r
+      try {\r
+        while (true) {\r
+          if (isEnabled) {\r
+            synchronized (WorkflowTask.workflowTasksByExecutionId) {\r
+              Set<Entry<String, List<WorkflowTask>>> set =\r
+                  WorkflowTask.workflowTasksByExecutionId.entrySet();\r
+\r
+              for (Entry<String, List<WorkflowTask>> entry : set) {\r
+                String processInstanceId = entry.getKey();\r
+                List<WorkflowTask> workflowTasks = entry.getValue();\r
+\r
+                ProcessInstance processInstance =\r
+                    runtimeService\r
+                        .createProcessInstanceQuery()\r
+                        .processInstanceId(processInstanceId)\r
+                        .singleResult();\r
+\r
+                if (processInstance == null) {\r
+                  System.out.println("Cleaning up WorkflowTasks under processInstanceId, " + processInstanceId);\r
+                  workflowTasks.forEach(WorkflowTask::shutdown);\r
+                }\r
+              }\r
+            }\r
+          }\r
+          Thread.sleep(10000);\r
+        }\r
+      } catch (InterruptedException e) {\r
+        e.printStackTrace();\r
+      }\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/service/ProcessEngineAwareService.java b/otf-camunda/src/main/java/org/oran/otf/camunda/service/ProcessEngineAwareService.java
new file mode 100644 (file)
index 0000000..6c8215b
--- /dev/null
@@ -0,0 +1,68 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.camunda.service;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import java.util.Optional;\r
+import org.camunda.bpm.engine.ProcessEngineServices;\r
+import org.camunda.bpm.engine.ProcessEngines;\r
+import org.springframework.stereotype.Service;\r
+\r
+/**\r
+ * Base class for services that must be process-engine aware. The only process engine currently\r
+ * supported is the "default" process engine.\r
+ */\r
+@Service\r
+public class ProcessEngineAwareService {\r
+\r
+  //  private final String processEngineName = OTFProcessEngineConfiguration.processEngineName;\r
+  private final String processEngineName = OtfCamundaConfiguration.processEngineName;\r
+  private volatile Optional<ProcessEngineServices> pes4junit = Optional.empty();\r
+\r
+  /**\r
+   * Gets the process engine name.\r
+   *\r
+   * @return the process engine name\r
+   */\r
+  public String getProcessEngineName() {\r
+    return processEngineName;\r
+  }\r
+\r
+  /**\r
+   * Gets process engine services.\r
+   *\r
+   * @return process engine services\r
+   */\r
+  public ProcessEngineServices getProcessEngineServices() {\r
+    return pes4junit.orElse(ProcessEngines.getProcessEngine(getProcessEngineName()));\r
+  }\r
+\r
+  /**\r
+   * Allows a particular process engine to be specified, overriding the usual process engine lookup\r
+   * by name. Intended primarily for the unit test environment.\r
+   *\r
+   * @param pes process engine services\r
+   */\r
+  public void setProcessEngineServices4junit(ProcessEngineServices pes) {\r
+    pes4junit = Optional.ofNullable(pes);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowProcessor.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowProcessor.java
new file mode 100644 (file)
index 0000000..10a1dfd
--- /dev/null
@@ -0,0 +1,526 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * ================================================================================\r
+ * Modifications Copyright (c) 2019 Samsung\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.camunda.workflow;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.exception.WorkflowProcessorException;\r
+import org.oran.otf.camunda.model.ExecutionConstants.ExecutionVariable;\r
+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;\r
+import org.oran.otf.camunda.model.WorkflowResponse;\r
+import org.oran.otf.camunda.service.ProcessEngineAwareService;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;\r
+import org.oran.otf.common.model.*;\r
+import org.oran.otf.common.model.historic.TestDefinitionHistoric;\r
+import org.oran.otf.common.model.historic.TestInstanceHistoric;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.repository.*;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.common.utility.database.Generic;\r
+import org.oran.otf.common.utility.permissions.PermissionChecker;\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import com.mongodb.client.result.UpdateResult;\r
+import java.util.ArrayList;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Objects;\r
+import java.util.UUID;\r
+import org.bson.types.ObjectId;\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RepositoryService;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.repository.ProcessDefinition;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.camunda.bpm.engine.variable.VariableMap;\r
+import org.camunda.bpm.engine.variable.Variables;\r
+import org.camunda.bpm.engine.variable.impl.VariableMapImpl;\r
+import org.oran.otf.common.model.*;\r
+import org.oran.otf.common.repository.*;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class WorkflowProcessor extends ProcessEngineAwareService {\r
+\r
+    private static final String logPrefix = Utility.getLoggerPrefix();\r
+    private static final Logger logger = LoggerFactory.getLogger(WorkflowProcessor.class);\r
+\r
+    @Autowired\r
+    GroupRepository groupRepository;\r
+    @Autowired\r
+    TestDefinitionRepository testDefinitionRepository;\r
+    @Autowired\r
+    TestInstanceRepository testInstanceRepository;\r
+    @Autowired\r
+    UserRepository userRepository;\r
+    @Autowired\r
+    TestExecutionRepository testExecutionRepository;\r
+    @Autowired\r
+    MongoTemplate mongoOperation;\r
+    @Autowired\r
+    WorkflowUtility workflowUtility;\r
+\r
+    private RuntimeService runtimeService;\r
+    private RepositoryService repositoryService;\r
+\r
+    // Note: the business key is used to identify the process in unit tests\r
+    protected static String getBusinessKey(Map<String, Object> inputVariables) {\r
+        return getOrCreate(inputVariables, "otf-business-key");\r
+    }\r
+\r
+    protected static Map<String, Object> getInputVariables(VariableMapImpl variableMap) {\r
+        Map<String, Object> inputVariables = new HashMap<>();\r
+        @SuppressWarnings("unchecked")\r
+        Map<String, Object> vMap = (Map<String, Object>) variableMap.get("variables");\r
+        for (Map.Entry<String, Object> entry : vMap.entrySet()) {\r
+            String vName = entry.getKey();\r
+            Object value = entry.getValue();\r
+            @SuppressWarnings("unchecked")\r
+            Map<String, Object> valueMap = (Map<String, Object>) value; // value, type\r
+            inputVariables.put(vName, valueMap.get("value"));\r
+        }\r
+        return inputVariables;\r
+    }\r
+\r
+    protected static String getOrCreate(Map<String, Object> inputVariables, String key) {\r
+        String value = Objects.toString(inputVariables.get(key), null);\r
+        if (value == null) {\r
+            value = UUID.randomUUID().toString();\r
+            inputVariables.put(key, value);\r
+        }\r
+        return value;\r
+    }\r
+\r
+    private static void buildVariable(\r
+        String key, String value, Map<String, Object> variableValueType) {\r
+        Map<String, Object> host = new HashMap<>();\r
+        host.put("value", value);\r
+        host.put("type", "String");\r
+        variableValueType.put(key, host);\r
+    }\r
+\r
+    @EventListener(ApplicationReadyEvent.class)\r
+    private void initialize() {\r
+        if (this.runtimeService == null) {\r
+            this.runtimeService =\r
+                BpmPlatform.getProcessEngineService()\r
+                    .getProcessEngine(OtfCamundaConfiguration.processEngineName)\r
+                    .getRuntimeService();\r
+        }\r
+        if (this.repositoryService == null) {\r
+            this.repositoryService =\r
+                BpmPlatform.getProcessEngineService()\r
+                    .getProcessEngine(OtfCamundaConfiguration.processEngineName)\r
+                    .getRepositoryService();\r
+        }\r
+    }\r
+\r
+    public TestExecution processWorkflowRequest(WorkflowRequest request)\r
+        throws WorkflowProcessorException {\r
+\r
+        // Check if the test instance exists.\r
+        TestInstance testInstance =\r
+            Generic.findByIdGeneric(testInstanceRepository, request.getTestInstanceId());\r
+        if (testInstance == null) {\r
+            WorkflowResponse response = new WorkflowResponse();\r
+            response.setMessage(\r
+                String.format(\r
+                    "Test instance with identifier %s was not found.",\r
+                    request.getTestInstanceId().toString()));\r
+            response.setMessageCode(404);\r
+            response.setResponse("Unable to start the test instance.");\r
+            TestExecution testExecution = generateTestExecution(request, null, null, null);\r
+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);\r
+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+            response.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(response);\r
+        }\r
+\r
+        // Override the test data and vth input of the instance if the request contains the data.\r
+        Map<String, Object> vthInput =\r
+                request.getVthInput() == null ? testInstance.getVthInput() : request.getVthInput();\r
+        Map<String, Object> testData =\r
+                request.getTestData() == null ? testInstance.getTestData() : request.getTestData();\r
+        Map<String, ParallelFlowInput> plfoInput =\r
+                request.getPfloInput() == null ? testInstance.getPfloInput() : request.getPfloInput();\r
+\r
+        testInstance.setVthInput((HashMap<String, Object>) vthInput);\r
+        testInstance.setTestData((HashMap<String, Object>) testData);\r
+        testInstance.setPfloInput((HashMap<String, ParallelFlowInput>) plfoInput);\r
+\r
+\r
+        // Check if the test definition linked to the test instance is also present.\r
+        TestDefinition testDefinition =\r
+            Generic.findByIdGeneric(testDefinitionRepository, testInstance.getTestDefinitionId());\r
+        if (testDefinition == null) {\r
+            WorkflowResponse response = new WorkflowResponse();\r
+            response.setMessage(\r
+                String.format(\r
+                    "Test definition with identifier %s was not found.",\r
+                    testInstance.getTestDefinitionId().toString()));\r
+            response.setMessageCode(404);\r
+            response.setResponse("Unable to start the test instance.");\r
+            TestExecution testExecution = generateTestExecution(request, testInstance, null, null);\r
+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);\r
+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+            response.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(response);\r
+        }\r
+\r
+        // is using latest defintion, verify that the processDefinitionId within camunda is present in\r
+        // the test definition bpmn instance list\r
+        if (testInstance.isUseLatestTestDefinition()) {\r
+            String processDefinitionId =\r
+                findLatestProcessDefinition(testDefinition.getProcessDefinitionKey());\r
+            boolean isBpmnInstancePresent =\r
+                verifyIdExistsInTestDefinition(testDefinition, processDefinitionId);\r
+            if (isBpmnInstancePresent) {\r
+                testInstance.setProcessDefinitionId(processDefinitionId);\r
+            } else {\r
+                WorkflowResponse response = new WorkflowResponse();\r
+                response.setMessage(\r
+                    String.format(\r
+                        "Latest Test Definition does not exist for key %s.",\r
+                        testDefinition.getProcessDefinitionKey()));\r
+                response.setMessageCode(404);\r
+                response.setResponse("Unable to start the test instance.");\r
+                TestExecution testExecution =\r
+                    generateTestExecution(request, testInstance, testDefinition, null);\r
+                testExecution.setTestResult(TestResult.DOES_NOT_EXIST);\r
+                testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+                response.setTestExecution(testExecution);\r
+                throw new WorkflowProcessorException(response);\r
+            }\r
+        }\r
+\r
+        // Check if the entity making the request has permission to run the test instance.\r
+        User executor = Generic.findByIdGeneric(userRepository, request.getExecutorId());\r
+        if (executor == null) {\r
+            WorkflowResponse response = new WorkflowResponse();\r
+            response.setMessage(\r
+                String\r
+                    .format("User with id %s was not found.", request.getExecutorId().toString()));\r
+            response.setMessageCode(404);\r
+            response.setResponse("Unable to start the test instance.");\r
+            TestExecution testExecution =\r
+                generateTestExecution(request, testInstance, testDefinition, null);\r
+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);\r
+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+            response.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(response);\r
+        }\r
+//        if (!workflowUtility.hasPermission(executor, testInstance)) {\r
+//            WorkflowResponse response = new WorkflowResponse();\r
+//            response.setMessage(\r
+//                String.format(\r
+//                    "The user with email %s does not have permission to execute test instance with id: %s.",\r
+//                    executor.getEmail(), testInstance.get_id().toString()));\r
+//            response.setMessageCode(401);\r
+//            response.setResponse("Unauthorized to execute the test instance.");\r
+//            TestExecution testExecution =\r
+//                generateTestExecution(request, testInstance, testDefinition, executor);\r
+//            testExecution.setTestResult(TestResult.UNAUTHORIZED);\r
+//            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+//            response.setTestExecution(testExecution);\r
+//            throw new WorkflowProcessorException(response);\r
+//        }\r
+        Group testInstanceGroup = groupRepository.findById(testInstance.getGroupId().toString()).orElse(null);\r
+        if(testInstanceGroup == null){\r
+            WorkflowResponse response = new WorkflowResponse();\r
+            response.setMessage(\r
+                    String.format("unable to find test instance group. Group id: %s",testInstance.getGroupId().toString()));\r
+            response.setMessageCode(404);\r
+            response.setResponse("unable to find test instance group");\r
+            TestExecution testExecution = generateTestExecution(request,testInstance,testDefinition,executor);\r
+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);\r
+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+            response.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(response);\r
+        }\r
+        if (!PermissionChecker.hasPermissionTo(executor,testInstanceGroup, UserPermission.Permission.EXECUTE,groupRepository)){\r
+            WorkflowResponse response = new WorkflowResponse();\r
+            response.setMessage(\r
+                    String.format(\r
+                            "User with email: %s does not have execute permission on test instance group with id: %s",\r
+                            executor.getEmail(),testInstance.getGroupId().toString()));\r
+            response.setMessageCode(401);\r
+            response.setResponse("unauthorized to execute test instance");\r
+            TestExecution testExecution = generateTestExecution(request,testInstance,testDefinition,executor);\r
+            testExecution.setTestResult(TestResult.UNAUTHORIZED);\r
+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));\r
+            response.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(response);\r
+        }\r
+\r
+        // Generate a testExecution with a historic copy of the test instance, test definition, and the\r
+        // email of the person executing the test.\r
+        TestExecution testExecution =\r
+            generateTestExecution(request, testInstance, testDefinition, executor);\r
+\r
+        // Prepare the test details, test result, test execution, and vth input variables for the\r
+        // process instance.\r
+        VariableMap variableMap =\r
+            Variables.createVariables()\r
+                .putValueTyped(\r
+                    ExecutionVariable.TEST_DETAILS,\r
+                    Variables.objectValue(testExecution.getTestDetails()).create())\r
+                .putValueTyped(\r
+                    ExecutionVariable.TEST_RESULT,\r
+                    Variables.objectValue(testExecution.getTestResult()).create())\r
+                .putValueTyped(\r
+                    ExecutionVariable.TEST_RESULT_MESSAGE,\r
+                    Variables.objectValue(testExecution.getTestResultMessage()).create())\r
+                .putValueTyped(ExecutionVariable.VTH_INPUT,\r
+                    Variables.objectValue(vthInput).create())\r
+                .putValueTyped(ExecutionVariable.TEST_DATA,\r
+                    Variables.objectValue(testData).create())\r
+                .putValue(\r
+                    ExecutionVariable.TEST_EXECUTION,\r
+                    Variables.objectValue(testExecution)\r
+                        .serializationDataFormat(Variables.SerializationDataFormats.JAVA)\r
+                        .create())\r
+                .putValue(\r
+                    ExecutionVariable.PFLO_INPUT,\r
+                    Variables.objectValue(plfoInput)\r
+                        .serializationDataFormat(Variables.SerializationDataFormats.JAVA)\r
+                        .create());\r
+\r
+        if (testInstance.isUseLatestTestDefinition()) {\r
+            return startProcessByKey(\r
+                testDefinition.getProcessDefinitionKey(), variableMap, testExecution);\r
+        } else {\r
+            return startProcessById(testInstance.getProcessDefinitionId(), variableMap,\r
+                testExecution);\r
+        }\r
+    }\r
+\r
+    public TestExecution startProcessByKey(\r
+        String processKey, Map<String, Object> variableMap, TestExecution testExecution) {\r
+        try {\r
+            logger.info(\r
+                "***OTF startProcessInstanceByKey with processKey: {} and variables: {}",\r
+                processKey,\r
+                variableMap);\r
+\r
+            // Set the start time as close to the runtime service start function.\r
+            testExecution.setStartTime(new Date(System.currentTimeMillis()));\r
+            testExecutionRepository.insert(testExecution);\r
+\r
+            ProcessInstance processInstance =\r
+                runtimeService.startProcessInstanceByKey(\r
+                    processKey, testExecution.getBusinessKey(), variableMap);\r
+\r
+            // Update the test execution object with the processInstanceId after the processInstanceId is\r
+            // available.\r
+            testExecution.setProcessInstanceId(processInstance.getProcessInstanceId());\r
+            Query query = new Query();\r
+            query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));\r
+            // Also add businessKey as a criteria because the object won't be found if the business key\r
+            // was somehow modified in the workflow.\r
+            query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));\r
+            Update update = new Update();\r
+            update.set("processInstanceId", processInstance.getProcessInstanceId());\r
+            UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+            // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+            if (result.getMatchedCount() == 0) {\r
+                throw new TestExecutionException(\r
+                    String.format(\r
+                        "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+                        testExecution.get_id(), testExecution.getBusinessKey()));\r
+            } else if (result.getModifiedCount() == 0) {\r
+                throw new TestExecutionException(\r
+                    "Unable to persist the testExecution to the database.");\r
+            }\r
+\r
+            logger.debug(\r
+                logPrefix\r
+                    + "Process "\r
+                    + processKey\r
+                    + ":"\r
+                    + processInstance.getProcessInstanceId()\r
+                    + " "\r
+                    + (processInstance.isEnded() ? "ENDED" : "RUNNING"));\r
+        } catch (Exception e) {\r
+            WorkflowResponse workflowResponse = new WorkflowResponse();\r
+            workflowResponse.setResponse("Error occurred while executing the process: " + e);\r
+            workflowResponse.setProcessInstanceId(testExecution.getProcessInstanceId());\r
+            workflowResponse.setMessageCode(500);\r
+            workflowResponse.setMessage("Failed to execute test instance: " + e.getMessage());\r
+            testExecution.setTestResult(TestResult.FAILED);\r
+            testExecution\r
+                .setTestDetails(generateTestDetailsWithMessage(workflowResponse.getMessage()));\r
+            workflowResponse.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(workflowResponse);\r
+        }\r
+\r
+        return testExecution;\r
+    }\r
+\r
+    private TestExecution startProcessById(\r
+        String processId, Map<String, Object> variableMap, TestExecution testExecution) {\r
+        try {\r
+            logger.debug(\r
+                "***OTF startProcessInstanceById with processId: {} and variables: {}",\r
+                processId,\r
+                variableMap);\r
+\r
+            // Set the start time as close to the runtime service start function.\r
+            testExecution.setStartTime(new Date(System.currentTimeMillis()));\r
+            testExecutionRepository.insert(testExecution);\r
+\r
+            ProcessInstance processInstance =\r
+                runtimeService.startProcessInstanceById(\r
+                    processId, testExecution.getBusinessKey(), variableMap);\r
+\r
+            // Update the test execution object with the processInstanceId after the processInstanceId is\r
+            // available.\r
+            testExecution.setProcessInstanceId(processInstance.getProcessInstanceId());\r
+            Query query = new Query();\r
+            query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));\r
+            // Also add businessKey as a criteria because the object won't be found if the business key\r
+            // was somehow modified in the workflow.\r
+            query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));\r
+            Update update = new Update();\r
+            update.set("processInstanceId", processInstance.getProcessInstanceId());\r
+            UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+            // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+            if (result.getMatchedCount() == 0) {\r
+                throw new TestExecutionException(\r
+                    String.format(\r
+                        "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+                        testExecution.get_id(), testExecution.getBusinessKey()));\r
+            } else if (result.getModifiedCount() == 0) {\r
+                throw new TestExecutionException(\r
+                    "Unable to persist the testExecution to the database.");\r
+            }\r
+\r
+            logger.debug(\r
+                logPrefix\r
+                    + "Process "\r
+                    + processInstance.getProcessInstanceId()\r
+                    + ":"\r
+                    + processInstance.getProcessInstanceId()\r
+                    + " "\r
+                    + (processInstance.isEnded() ? "ENDED" : "RUNNING"));\r
+        } catch (Exception e) {\r
+            WorkflowResponse workflowResponse = new WorkflowResponse();\r
+            workflowResponse.setResponse("Error occurred while executing the process: " + e);\r
+            workflowResponse.setProcessInstanceId(testExecution.getProcessInstanceId());\r
+            workflowResponse.setMessageCode(500);\r
+            workflowResponse.setMessage("Failed to execute test instance: " + e.getMessage());\r
+            testExecution.setTestResult(TestResult.FAILED);\r
+            testExecution\r
+                .setTestDetails(generateTestDetailsWithMessage(workflowResponse.getMessage()));\r
+            workflowResponse.setTestExecution(testExecution);\r
+            throw new WorkflowProcessorException(workflowResponse);\r
+        }\r
+\r
+        return testExecution;\r
+    }\r
+\r
+    private TestExecution generateTestExecution(\r
+        WorkflowRequest request,\r
+        TestInstance testInstance,\r
+        TestDefinition testDefinition,\r
+        User executor) {\r
+        TestExecution testExecution = new TestExecution();\r
+        testExecution.set_id(new ObjectId());\r
+        testExecution.setExecutorId(request.getExecutorId());\r
+        testExecution.setAsync(request.isAsync());\r
+        testExecution.setStartTime(null);\r
+        testExecution.setTestDetails(new HashMap<>());\r
+        testExecution.setTestResult(TestResult.UNKNOWN);\r
+        testExecution.setTestResultMessage("");\r
+        testExecution.setProcessInstanceId(null);\r
+        testExecution.setBusinessKey(UUID.randomUUID().toString());\r
+        testExecution.setTestHeadResults(new ArrayList<>());\r
+        testExecution.setTestInstanceResults(new ArrayList<>());\r
+        if (testInstance != null) {\r
+            testExecution.setGroupId(testInstance.getGroupId());\r
+            TestInstanceHistoric testInstanceHistoric = new TestInstanceHistoric(testInstance);\r
+            testExecution.setHistoricTestInstance(testInstanceHistoric);\r
+        }\r
+        if (testDefinition != null && testInstance != null) {\r
+            TestDefinitionHistoric testDefinitionHistoric =\r
+                new TestDefinitionHistoric(testDefinition, testInstance.getProcessDefinitionId());\r
+            testExecution.setHistoricTestDefinition(testDefinitionHistoric);\r
+        }\r
+        if (executor != null) {\r
+            testExecution.setHistoricEmail(executor.getEmail());\r
+        }\r
+        return testExecution;\r
+    }\r
+\r
+    private Map<String, Object> generateTestDetailsWithMessage(String message) {\r
+        Map<String, Object> map = new HashMap<>();\r
+        map.put("message", message);\r
+        return map;\r
+    }\r
+\r
+    private String findLatestProcessDefinition(String processDefinitionKey) {\r
+        logger.info("Before find process definition key query.");\r
+        ProcessDefinition definition =\r
+            repositoryService\r
+                .createProcessDefinitionQuery()\r
+                .processDefinitionKey(processDefinitionKey)\r
+                .latestVersion()\r
+                .singleResult();\r
+        logger.info("After find process definition key query.");\r
+        String processDefinitionId = null;\r
+        if (definition != null) {\r
+            processDefinitionId = definition.getId();\r
+        }\r
+        return processDefinitionId;\r
+    }\r
+\r
+    private boolean verifyIdExistsInTestDefinition(\r
+        TestDefinition definition, String processDefinitionId) {\r
+        if (processDefinitionId == null || definition == null) {\r
+            return false;\r
+        }\r
+\r
+        List<BpmnInstance> bpmnInstances = definition.getBpmnInstances();\r
+        BpmnInstance bpmnInstance =\r
+            bpmnInstances.stream()\r
+                .filter(\r
+                    _bpmnInstance -> {\r
+                        return _bpmnInstance.isDeployed()\r
+                            && _bpmnInstance.getProcessDefinitionId() != null\r
+                            && _bpmnInstance.getProcessDefinitionId().equals(processDefinitionId);\r
+                    })\r
+                .findFirst()\r
+                .orElse(null);\r
+        return bpmnInstance != null;\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowRequest.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowRequest.java
new file mode 100644 (file)
index 0000000..fa5f10e
--- /dev/null
@@ -0,0 +1,182 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.workflow;\r
+\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import com.fasterxml.jackson.annotation.JsonCreator;\r
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;\r
+import com.fasterxml.jackson.annotation.JsonProperty;\r
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;\r
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;\r
+import java.io.Serializable;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+@JsonIgnoreProperties(ignoreUnknown = true)\r
+public class WorkflowRequest implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private boolean async = false;\r
+  @JsonSerialize(using = ToStringSerializer.class)\r
+  private ObjectId executorId = null;\r
+\r
+  @JsonSerialize(using = ToStringSerializer.class)\r
+  private ObjectId testInstanceId = null;\r
+\r
+  private Map<String, ParallelFlowInput> pfloInput = null;\r
+  private Map<String, Object> testData = null;\r
+  private Map<String, Object> vthInput = null;\r
+  private long maxExecutionTimeInMillis = 0L;\r
+\r
+  public WorkflowRequest() throws Exception {\r
+    //this.validate();\r
+  }\r
+\r
+  public WorkflowRequest(\r
+      boolean async,\r
+      String executorId,\r
+      String testInstanceId,\r
+      long maxExecutionTimeInMillis) {\r
+    this.async = async;\r
+    this.executorId = new ObjectId(executorId);\r
+    this.testInstanceId = new ObjectId(testInstanceId);\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public WorkflowRequest(\r
+      boolean async,\r
+      ObjectId executorId,\r
+      ObjectId testInstanceId,\r
+      Map<String, ParallelFlowInput> pfloInput,\r
+      Map<String, Object> testData,\r
+      Map<String, Object> vthInput,\r
+      int maxExecutionTimeInMillis)\r
+      throws Exception {\r
+    this.async = async;\r
+    this.executorId = executorId;\r
+    this.testInstanceId = testInstanceId;\r
+    this.pfloInput = pfloInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+\r
+    this.validate();\r
+  }\r
+\r
+  @JsonCreator\r
+  public WorkflowRequest(\r
+      @JsonProperty(value = "async", required = false) boolean async,\r
+      @JsonProperty(value = "executorId", required = true) String executorId,\r
+      @JsonProperty(value = "testInstanceId", required = true) String testInstanceId,\r
+      @JsonProperty(value = "pfloInput", required = false) Map<String, ParallelFlowInput> pfloInput,\r
+      @JsonProperty(value = "testData", required = false) Map<String, Object> testData,\r
+      @JsonProperty(value = "vthInput", required = false) Map<String, Object> vthInput,\r
+      @JsonProperty(value = "maxExecutionTimeInMillis", required = false)\r
+          int maxExecutionTimeInMillis) throws Exception {\r
+    this.async = async;\r
+    this.executorId = new ObjectId(executorId);\r
+    this.testInstanceId = new ObjectId(testInstanceId);\r
+    this.pfloInput = pfloInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+\r
+    this.validate();\r
+  }\r
+\r
+  private void validate() throws Exception {\r
+    String missingFieldFormat = "Missing required field %s.";\r
+//    if (this.executorId == null) {\r
+//      throw new Exception(String.format(missingFieldFormat, "executorId"));\r
+//    }\r
+\r
+    if (this.testInstanceId == null) {\r
+      throw new Exception(String.format(missingFieldFormat, "testInstanceId"));\r
+    }\r
+\r
+    if (this.maxExecutionTimeInMillis < 0L) {\r
+      this.maxExecutionTimeInMillis = 0L;\r
+    }\r
+  }\r
+\r
+  public boolean isAsync() {\r
+    return async;\r
+  }\r
+\r
+  public void setAsync(boolean async) {\r
+    this.async = async;\r
+  }\r
+\r
+  public ObjectId getExecutorId() {\r
+    return executorId;\r
+  }\r
+\r
+  public void setExecutorId(ObjectId executorId) {\r
+    this.executorId = executorId;\r
+  }\r
+\r
+  public ObjectId getTestInstanceId() {\r
+    return testInstanceId;\r
+  }\r
+\r
+  public void setTestInstanceId(String testInstanceId) {\r
+    this.testInstanceId = new ObjectId(testInstanceId);\r
+  }\r
+\r
+  public void setTestInstanceId(ObjectId testInstanceId) {\r
+    this.testInstanceId = testInstanceId;\r
+  }\r
+\r
+  public Map<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(Map<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public Map<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(Map<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public Map<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(Map<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public long getMaxExecutionTimeInMillis() {\r
+    return maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/ExternalTaskIncidentHandler.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/ExternalTaskIncidentHandler.java
new file mode 100644 (file)
index 0000000..0e7d2ca
--- /dev/null
@@ -0,0 +1,139 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.workflow.handler;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.service.impl.DeleteProcessInstanceServiceImpl;\r
+import com.mongodb.client.result.UpdateResult;\r
+\r
+import java.util.Date;\r
+import java.util.List;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.impl.incident.IncidentContext;\r
+import org.camunda.bpm.engine.impl.incident.IncidentHandler;\r
+import org.camunda.bpm.engine.runtime.Execution;\r
+import org.camunda.bpm.engine.runtime.Incident;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class ExternalTaskIncidentHandler implements IncidentHandler {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(ExternalTaskIncidentHandler.class);\r
+  private static final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+  @Autowired\r
+  private TestExecutionRepository testExecutionRepository;\r
+  @Autowired\r
+  private MongoTemplate mongoOperation;\r
+  @Autowired\r
+  private DeleteProcessInstanceServiceImpl deleteProcessInstanceService;\r
+\r
+  @Override\r
+  public String getIncidentHandlerType() {\r
+    return Incident.EXTERNAL_TASK_HANDLER_TYPE;\r
+  }\r
+\r
+  @Override\r
+  public Incident handleIncident(IncidentContext context, String message) {\r
+    //need to get process instance id from executionid (parent process)\r
+    String executionId = context.getExecutionId();\r
+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+\r
+    Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();\r
+    String processInstanceId = execution.getProcessInstanceId();\r
+    TestExecution testExecution =\r
+        testExecutionRepository.findFirstByProcessInstanceId(processInstanceId).orElse(null);\r
+\r
+    if (testExecution == null) {\r
+      String error =\r
+          String.format(\r
+              "%sUnable to find testExecution with processInstanceId %s. This process instance will forcefully be terminated to avoid a rogue process.",\r
+              logPrefix, processInstanceId);\r
+      logger.error(error);\r
+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, error);\r
+    } else {\r
+      if(!testExecution.getTestResult().equals(TestResult.TERMINATED)){\r
+        updateTestResult(testExecution, TestResult.WORKFLOW_ERROR, message);\r
+      }\r
+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, message);\r
+\r
+      List<WorkflowTask> workflowTasks =\r
+          WorkflowTask.workflowTasksByExecutionId.getOrDefault(processInstanceId, null);\r
+\r
+      if (workflowTasks != null) {\r
+        logger.debug("Forcefully terminating workflow tasks for processInstanceId: " + processInstanceId);\r
+        for (WorkflowTask workflowTask : workflowTasks) {\r
+          workflowTask.shutdown(true);\r
+        }\r
+      }\r
+    }\r
+\r
+    return null;\r
+  }\r
+\r
+  private void updateTestResult(TestExecution testExecution, String testResult, String testResultMessage) {\r
+    // Set the test result\r
+    testExecution.setTestResult(testResult);\r
+    testExecution.setTestResultMessage(testResultMessage);\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));\r
+    // Also add businessKey as a criteria because the object won't be found if the business key\r
+    // was somehow modified in the workflow.\r
+    query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testResult", testExecution.getTestResult());\r
+    update.set("testResultMessage", testExecution.getTestResultMessage());\r
+    update.set("endTime", new Date(System.currentTimeMillis()));\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+    // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+    if (result.getMatchedCount() == 0) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+              testExecution.get_id(), testExecution.getBusinessKey()));\r
+    } else if (result.getModifiedCount() == 0) {\r
+      throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public void resolveIncident(IncidentContext context) {\r
+    //    logger.info("incident resolved");\r
+  }\r
+\r
+  @Override\r
+  public void deleteIncident(IncidentContext context) {\r
+    //    logger.info("incident deleted");\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/FailedJobIncidentHandler.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/FailedJobIncidentHandler.java
new file mode 100644 (file)
index 0000000..f01d550
--- /dev/null
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.workflow.handler;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.service.impl.DeleteProcessInstanceServiceImpl;\r
+import com.google.common.base.Strings;\r
+import com.mongodb.client.result.UpdateResult;\r
+\r
+import java.util.Date;\r
+import java.util.List;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.impl.incident.IncidentContext;\r
+import org.camunda.bpm.engine.impl.incident.IncidentHandler;\r
+import org.camunda.bpm.engine.runtime.Execution;\r
+import org.camunda.bpm.engine.runtime.Incident;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class FailedJobIncidentHandler implements IncidentHandler {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(FailedJobIncidentHandler.class);\r
+  private static final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+  @Autowired\r
+  private TestExecutionRepository testExecutionRepository;\r
+  @Autowired\r
+  private MongoTemplate mongoOperation;\r
+  @Autowired\r
+  private DeleteProcessInstanceServiceImpl deleteProcessInstanceService;\r
+\r
+  @Override\r
+  public String getIncidentHandlerType() {\r
+    return Incident.FAILED_JOB_HANDLER_TYPE;\r
+  }\r
+\r
+  @Override\r
+  public Incident handleIncident(IncidentContext context, String message) {\r
+    String executionId = context.getExecutionId();\r
+    if (Strings.isNullOrEmpty(executionId)) {\r
+      return null;\r
+    }\r
+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+\r
+    Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();\r
+    String processInstanceId = execution.getProcessInstanceId();\r
+    TestExecution testExecution =\r
+        testExecutionRepository.findFirstByProcessInstanceId(processInstanceId).orElse(null);\r
+\r
+    if (testExecution == null) {\r
+      String error = String.format(\r
+          "%sUnable to find testExecution with processInstanceId %s. This process instance will forcefully be terminated to avoid a rogue process.",\r
+          logPrefix, processInstanceId);\r
+      logger.error(error);\r
+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, error);\r
+\r
+    } else {\r
+      if(!testExecution.getTestResult().equals(TestResult.TERMINATED)){\r
+        updateTestResult(testExecution, TestResult.WORKFLOW_ERROR, message);\r
+      }\r
+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, message);\r
+\r
+    }\r
+\r
+    List<WorkflowTask> workflowTasks =\r
+        WorkflowTask.workflowTasksByExecutionId.getOrDefault(processInstanceId, null);\r
+\r
+    if (workflowTasks != null) {\r
+      logger.debug("Forcefully terminating workflow tasks for processInstanceId: " + processInstanceId);\r
+      for (WorkflowTask workflowTask : workflowTasks) {\r
+        workflowTask.shutdown(true);\r
+      }\r
+    }\r
+\r
+    return null;\r
+  }\r
+\r
+  private void updateTestResult(TestExecution testExecution, String testResult, String testResultMessage) {\r
+    // Set the test result\r
+    testExecution.setTestResult(testResult);\r
+    testExecution.setTestResultMessage(testResultMessage);\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));\r
+    // Also add businessKey as a criteria because the object won't be found if the business key\r
+    // was somehow modified in the workflow.\r
+    query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testResult", testExecution.getTestResult());\r
+    update.set("testResultMessage", testExecution.getTestResultMessage());\r
+    update.set("endTime", new Date(System.currentTimeMillis()));\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+    // Check the status of the findAndUpdate database, and appropriately handle the errors.\r
+    if (result.getMatchedCount() == 0) {\r
+      throw new TestExecutionException(\r
+          String.format(\r
+              "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",\r
+              testExecution.get_id(), testExecution.getBusinessKey()));\r
+    } else if (result.getModifiedCount() == 0) {\r
+      throw new TestExecutionException("Unable to persist the testExecution to the database.");\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public void resolveIncident(IncidentContext context) {\r
+    //    logger.info("incident resolved");\r
+\r
+  }\r
+\r
+  @Override\r
+  public void deleteIncident(IncidentContext context) {\r
+    //    logger.info("incident deleted");\r
+\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/RsaEncryptDecrypt.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/RsaEncryptDecrypt.java
new file mode 100644 (file)
index 0000000..b90a8e5
--- /dev/null
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.workflow.utility;\r
+\r
+import java.security.KeyPair;\r
+import java.security.KeyPairGenerator;\r
+import java.security.NoSuchAlgorithmException;\r
+import javax.crypto.Cipher;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class RsaEncryptDecrypt {\r
+\r
+  private KeyPair keyPair;\r
+\r
+  public RsaEncryptDecrypt() throws NoSuchAlgorithmException {\r
+    this.keyPair = buildKeyPair();\r
+\r
+  }\r
+\r
+  private KeyPair buildKeyPair() throws NoSuchAlgorithmException {\r
+    final int keySize = 2048;\r
+    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");\r
+    keyPairGenerator.initialize(keySize);\r
+    return keyPairGenerator.genKeyPair();\r
+  }\r
+\r
+  public byte[] encrypt(String message) throws Exception {\r
+    Cipher cipher = Cipher.getInstance("RSA");\r
+    cipher.init(Cipher.ENCRYPT_MODE, this.keyPair.getPrivate());\r
+\r
+    return cipher.doFinal(message.getBytes());\r
+  }\r
+\r
+  public byte[] decrypt(byte[] encrypted) throws Exception {\r
+    Cipher cipher = Cipher.getInstance("RSA");\r
+    cipher.init(Cipher.DECRYPT_MODE, this.keyPair.getPublic());\r
+\r
+    return cipher.doFinal(encrypted);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowTask.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowTask.java
new file mode 100644 (file)
index 0000000..e7302e6
--- /dev/null
@@ -0,0 +1,169 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.workflow.utility;\r
+\r
+import org.oran.otf.common.utility.Utility;\r
+import com.google.common.base.Strings;\r
+import com.google.common.util.concurrent.ThreadFactoryBuilder;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.Future;\r
+import java.util.concurrent.ThreadFactory;\r
+import java.util.concurrent.ThreadPoolExecutor;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+\r
+public class WorkflowTask {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(WorkflowTask.class);\r
+  private static final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+  public static Map<String, List<WorkflowTask>> workflowTasksByExecutionId =\r
+      new ConcurrentHashMap<>();\r
+  // The processInstanceId of the Camunda process instance the thread pool is created under.\r
+  private final String processInstanceId;\r
+  // The pool service used to create the fixed thread pool.\r
+  private final ExecutorService pool;\r
+  // Used to keep track of all the tasks to be executed, which allows tasks to easily be deleted.\r
+  private List<Future<?>> futures;\r
+  // Used to determine if currently running threads should be interrupted\r
+  private boolean interruptOnFailure;\r
+\r
+  public WorkflowTask(String executionId, int threads, boolean interruptOnFailure) {\r
+    if (threads <= 0 || Strings.isNullOrEmpty(executionId)) {\r
+      this.processInstanceId = null;\r
+      this.pool = null;\r
+      return;\r
+    }\r
+\r
+    ThreadFactory namedThreadFactory =\r
+        new ThreadFactoryBuilder().setNameFormat(executionId + "-%d").build();\r
+\r
+    this.processInstanceId = executionId;\r
+    this.pool =\r
+        threads == 1\r
+            ? Executors.newSingleThreadExecutor()\r
+            : Executors.newFixedThreadPool(threads, namedThreadFactory);\r
+    this.futures = Collections.synchronizedList(new ArrayList<>());\r
+    this.interruptOnFailure = interruptOnFailure;\r
+\r
+    synchronized (WorkflowTask.workflowTasksByExecutionId) {\r
+      if (!WorkflowTask.workflowTasksByExecutionId.containsKey(this.processInstanceId)) {\r
+        List<WorkflowTask> list = new ArrayList<>();\r
+        list.add(this);\r
+        WorkflowTask.workflowTasksByExecutionId.put(\r
+            this.processInstanceId, Collections.synchronizedList(list));\r
+      } else {\r
+        WorkflowTask.workflowTasksByExecutionId.get(this.processInstanceId).add(this);\r
+      }\r
+    }\r
+  }\r
+\r
+  public void shutdown() {\r
+    this.shutdown(this.interruptOnFailure);\r
+  }\r
+\r
+  public void shutdown(boolean interruptOnFailure) {\r
+    if (interruptOnFailure) {\r
+      // Cancel currently executing tasks, and halt any waiting tasks.\r
+      pool.shutdownNow();\r
+    } else {\r
+      // Disable new tasks from being submitted, while allowing currently executing tasks to finish.\r
+      pool.shutdown();\r
+    }\r
+\r
+    try {\r
+      // Wait a while for existing tasks to terminate\r
+      if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {\r
+        for (Future<?> f : futures) {\r
+          f.cancel(interruptOnFailure);\r
+        }\r
+\r
+        // Wait a while for tasks to respond to being cancelled\r
+        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {\r
+          System.err.println("Pool did not terminate");\r
+        }\r
+      }\r
+    } catch (InterruptedException ie) {\r
+      // (Re-)Cancel if current thread also interrupted\r
+      pool.shutdownNow();\r
+      // Preserve interrupt status\r
+      // Thread.currentThread().interrupt();\r
+    }\r
+\r
+    workflowTasksByExecutionId.remove(this.processInstanceId);\r
+  }\r
+\r
+  public String getProcessInstanceId() {\r
+    return processInstanceId;\r
+  }\r
+\r
+  public ExecutorService getPool() {\r
+    return pool;\r
+  }\r
+\r
+  public List<Future<?>> getFutures() {\r
+    return futures;\r
+  }\r
+\r
+  public void setFutures(List<Future<?>> futures) {\r
+    this.futures = futures;\r
+  }\r
+\r
+  public static void printWorkflowTaskResources() {\r
+    for (Map.Entry<String, List<WorkflowTask>> entry : workflowTasksByExecutionId.entrySet()) {\r
+      logger.info(\r
+          "{}--------------Parent processInstanceId: {}--------------", logPrefix, entry.getKey());\r
+\r
+      List<WorkflowTask> workflowTasks =\r
+          workflowTasksByExecutionId.getOrDefault(entry.getKey(), null);\r
+      for (WorkflowTask task : workflowTasks) {\r
+        task.print();\r
+      }\r
+    }\r
+  }\r
+\r
+  public static void printThreadInformation() {\r
+    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();\r
+    for (Thread t : threadSet) {\r
+      if (t.getThreadGroup() == Thread.currentThread().getThreadGroup()) {\r
+        logger.info("{}{}", logPrefix, t.toString());\r
+      }\r
+    }\r
+  }\r
+\r
+  private void print() {\r
+    logger.info("%sWorkflowTask processInstanceId{})", this.processInstanceId);\r
+    if (this.pool instanceof ThreadPoolExecutor) {\r
+      ThreadPoolExecutor tpe = (ThreadPoolExecutor) pool;\r
+\r
+      logger.info("\tActive count: {}.", tpe.getActiveCount());\r
+      logger.info("\tTask status: {}/{}.", tpe.getCompletedTaskCount(), tpe.getTaskCount());\r
+      logger.info("\tPool size: {}.", tpe.getPoolSize());\r
+      logger.info("\tCore pool size: {}.", tpe.getCorePoolSize());\r
+      logger.info("\tQueue size: {}.", tpe.getQueue().size());\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowUtility.java b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowUtility.java
new file mode 100644 (file)
index 0000000..608470e
--- /dev/null
@@ -0,0 +1,291 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.camunda.workflow.utility;\r
+\r
+import static org.camunda.spin.Spin.JSON;\r
+\r
+import org.oran.otf.camunda.exception.TestExecutionException;\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.oran.otf.camunda.model.ExecutionConstants.ExecutionVariable;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.utility.Utility;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.camunda.spin.json.SpinJsonNode;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class WorkflowUtility {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(WorkflowUtility.class);\r
+  @Autowired\r
+  private RsaEncryptDecrypt rsaUtility;\r
+\r
+  public boolean verifyTestExecutionChecksum(\r
+      DelegateExecution execution, TestExecution testExecution) {\r
+    try {\r
+      byte[] enc = (byte[]) execution.getVariable(ExecutionVariable.TEST_EXECUTION);\r
+\r
+      String test = ""; // testExecution.createTestDescription();\r
+      String dec = new String(rsaUtility.decrypt(enc));\r
+      if (!dec.equals(test)) {\r
+        return false;\r
+        // throw new TestExecutionException("Modification Error: User modified platform data");\r
+      }\r
+    } catch (Exception e) {\r
+      logger.error(\r
+          execution.getCurrentActivityId()\r
+              + ": Failed to decrypt test execution. May have been tampered with.\n"\r
+              + e.getMessage());\r
+      return false;\r
+    }\r
+    return true;\r
+  }\r
+\r
+  public <T> T getExecutionVariable(Map<String, Object> variables, String key, Class<T> type) {\r
+    Object obj = variables.get(key);\r
+    if (obj == null) {\r
+      logger.error(String.format("Failed to get variable because the key %s does not exist.", key));\r
+    }\r
+    // return spin json nodes as maps\r
+    if (obj instanceof SpinJsonNode) {\r
+      SpinJsonNode node = (SpinJsonNode) obj;\r
+      if (!node.isObject()) {\r
+        throw new TestExecutionException(\r
+            "Unable to retrieve variable as type Map from the execution. Variable was set to SpinJsonNode");\r
+      }\r
+      Map<String, Object> map = (Map<String, Object>) node.mapTo(HashMap.class);\r
+    }\r
+\r
+    return type.isInstance(obj) ? type.cast(obj) : null;\r
+  }\r
+\r
+//  public boolean hasPermission(User user, TestInstance testInstance) {\r
+//    // Groups that the user holds a membership in.\r
+//    List<UserGroup> userGroups = user.getGroups();\r
+//    // The groupId associated with the test instance.\r
+//    ObjectId targetGroupId = testInstance.getGroupId();\r
+//    // Check if any of the groups has access to the test instance.\r
+//    UserGroup targetGroup =\r
+//        userGroups.stream()\r
+//            .filter(userGroup -> userGroup.getGroupId().equals(targetGroupId))\r
+//            .findAny()\r
+//            .orElse(null);\r
+//\r
+//    return targetGroup != null;\r
+//  }\r
+\r
+  public TestExecution getTestExecution(Map<String, Object> variables, String logPrefix)\r
+      throws TestExecutionException {\r
+    // Get the current test execution object.\r
+    TestExecution testExecution =\r
+        this.getExecutionVariable(variables, ExecutionVariable.TEST_EXECUTION, TestExecution.class);\r
+    // Perform a null-check to ensure it is available. It's critical to throw an exception if it\r
+    // is not available since the object is essential for results.\r
+    if (testExecution == null) {\r
+      logger.error(logPrefix + " Test execution is null.");\r
+      throw new TestExecutionException("The test execution was not found.");\r
+    }\r
+    return testExecution;\r
+  }\r
+\r
+  public Map<String, Object> getTestData(Map<String, Object> variables, String logPrefix)\r
+      throws TestExecutionException {\r
+    // Get vthInput from the Camunda execution variable map.\r
+    @SuppressWarnings({"unchecked"})\r
+    Map<String, Object> testData =\r
+        (Map<String, Object>)\r
+            this.getExecutionVariable(variables, ExecutionVariable.TEST_DATA, Map.class);\r
+\r
+    if (testData == null) {\r
+      throw new TestExecutionException(\r
+          "Unable to retrieve testData as type Map from the execution.");\r
+    }\r
+    return testData;\r
+  }\r
+\r
+  public Object getTestDataByActivity(\r
+      Map<String, Object> variables, String currentActivityId, String logPrefix)\r
+      throws TestExecutionException, NullPointerException {\r
+    // Get vthInput from the Camunda execution variable map.\r
+    @SuppressWarnings({"unchecked"})\r
+    Map<String, Object> testData =\r
+        (Map<String, Object>)\r
+            this.getExecutionVariable(variables, ExecutionVariable.TEST_DATA, Map.class);\r
+\r
+    if (testData == null) {\r
+      throw new TestExecutionException(\r
+          "Unable to retrieve testData as type Map from the execution.");\r
+    }\r
+    Object activityParameters = testData.get(currentActivityId);\r
+    if (activityParameters == null) {\r
+      throw new NullPointerException(\r
+          logPrefix\r
+              + String.format(\r
+              "A testData parameter was not found for the activityId, %s.", currentActivityId));\r
+    }\r
+    return activityParameters;\r
+  }\r
+\r
+\r
+  public Map<String, ParallelFlowInput> getPfloInputByActivity(\r
+      Map<String, Object> variables, String currentActivityId, String logPrefix)\r
+      throws TestExecutionException, NullPointerException {\r
+    // Get vthInput from the Camunda execution variable map.\r
+    @SuppressWarnings({"unchecked"})\r
+    Map<String, Object> pfloInput =\r
+        (Map<String, Object>)\r
+            this.getExecutionVariable(variables, ExecutionVariable.PFLO_INPUT, Map.class);\r
+\r
+    if (pfloInput == null) {\r
+      throw new TestExecutionException(\r
+          "Unable to retrieve testData as type Map from the execution.");\r
+    }\r
+    Map<String, ParallelFlowInput> activityParameters =\r
+        (Map<String, ParallelFlowInput>) pfloInput.get(currentActivityId);\r
+    if (activityParameters == null) {\r
+      throw new NullPointerException(\r
+          logPrefix\r
+              + String.format(\r
+              "A plfoInput parameter was not found for the activityId, %s.",\r
+              currentActivityId));\r
+    }\r
+    return activityParameters;\r
+  }\r
+\r
+  public List<Map<String, Object>> getVthInput(\r
+      Map<String, Object> variables, String currentActivityId, String logPrefix)\r
+      throws TestExecutionException, NullPointerException, IllegalArgumentException {\r
+    // Get vthInput from the Camunda execution variable map.\r
+    @SuppressWarnings({"unchecked"})\r
+    Map<String, Object> vthInput =\r
+        (Map<String, Object>)\r
+            this.getExecutionVariable(variables, ExecutionVariable.VTH_INPUT, Map.class);\r
+\r
+    if (vthInput == null) {\r
+      throw new TestExecutionException(\r
+          "Unable to retrieve vthInput as type Map from the execution.");\r
+    }\r
+\r
+    // Get the current activityId to use as a key to retrieve the vthInput for this task.\r
+    // vthInput is expected to be a JSON array of size [1, inf)\r
+    Object oActivityParameters = vthInput.get(currentActivityId);\r
+    // Throw an exception if no parameters were found for this activity.\r
+    if (oActivityParameters == null) {\r
+      throw new NullPointerException(\r
+          logPrefix\r
+              + String.format(\r
+              "A vthInput parameter was not found for the activityId, %s.", currentActivityId));\r
+    }\r
+\r
+    List<Map<String, Object>> lActivityParameters;\r
+    // Legacy hack\r
+    try {\r
+      @SuppressWarnings("unchecked")\r
+      Map<String, Object> mActivityParameters = new HashMap<>();\r
+      mActivityParameters.put("method", "post");\r
+      mActivityParameters.put("payload", Utility.toMap(oActivityParameters));\r
+      Map<String, Object> headers = new HashMap<>();\r
+      headers.put("Content-Type", "application/json");\r
+      mActivityParameters.put("headers", headers);\r
+      lActivityParameters = new ArrayList();\r
+      lActivityParameters.add(mActivityParameters);\r
+    } catch (Exception e) {\r
+      try {\r
+        // Try to convert the parameters to an array of "vthInput(s)"\r
+        lActivityParameters = (List<Map<String, Object>>) Utility.toList(oActivityParameters);\r
+      } catch (Exception ee) {\r
+        throw new IllegalArgumentException(\r
+            String.format("Unable to parse the value for vthInput[%s].", currentActivityId));\r
+      }\r
+    }\r
+    return lActivityParameters;\r
+  }\r
+\r
+  public String getTestResult(Map<String, Object> variables, String logPrefix) {\r
+    String testResult =\r
+        this.getExecutionVariable(variables, ExecutionVariable.TEST_RESULT, String.class);\r
+    // Set the test result to UNKNOWN\r
+    if (testResult == null) {\r
+      logger.debug(\r
+          logPrefix\r
+              + "Unable to retrieve test result as primitive type String. Setting result to unknown.");\r
+      testResult = ExecutionConstants.TestResult.UNKNOWN;\r
+    }\r
+    return testResult;\r
+  }\r
+\r
+  public String getTestResultMessage(Map<String, Object> variables, String logPrefix) {\r
+    String testResultMessage =\r
+            this.getExecutionVariable(variables, ExecutionVariable.TEST_RESULT_MESSAGE, String.class);\r
+    // Set the test result to UNKNOWN\r
+    if (testResultMessage == null) {\r
+      testResultMessage = "";\r
+//      logger.debug(\r
+//              logPrefix\r
+//                      + "Unable to retrieve test result message as primitive type String. Setting message to empty string.");\r
+//      testResultMessage = "";\r
+    }\r
+    return testResultMessage;\r
+  }\r
+\r
+  public Map<String, Object> getTestDetails(Map<String, Object> variables, String logPrefix)\r
+      throws TestExecutionException {\r
+    // Get test details as a String because it can be saved as one of many "JSON" types. Then try\r
+    // to convert it to a generic map.\r
+    String testDetailsString =\r
+        this.getExecutionVariable(variables, ExecutionVariable.TEST_DETAILS, String.class);\r
+    if (testDetailsString != null) {\r
+      // Use Spin to map the string to a Map.\r
+      @SuppressWarnings({"unchecked"})\r
+      Map<String, Object> mTestDetails;\r
+      try {\r
+        mTestDetails = JSON(testDetailsString).mapTo(HashMap.class);\r
+      } catch (Exception e) {\r
+        logger.error(\r
+            "Unable to convert testDetails to a map.\nError: "\r
+                + e.getMessage()\r
+                + "\ntestDetails: "\r
+                + testDetailsString);\r
+        mTestDetails = new HashMap<>();\r
+      }\r
+      return mTestDetails;\r
+    }\r
+\r
+    // get testDetails as a map.\r
+    @SuppressWarnings({"unchecked"})\r
+    Map<String, Object> testDetails =\r
+        (Map<String, Object>)\r
+            this.getExecutionVariable(variables, ExecutionVariable.TEST_DETAILS, Map.class);\r
+\r
+    if (testDetails == null) {\r
+      logger.debug(\r
+          logPrefix\r
+              + "Unable to retrieve test details as primitive type String. Setting to an empty JSON.");\r
+      testDetails = new HashMap<>();\r
+    }\r
+    return testDetails;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/Group.java b/otf-camunda/src/main/java/org/oran/otf/common/model/Group.java
new file mode 100644 (file)
index 0000000..93162f8
--- /dev/null
@@ -0,0 +1,109 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "groups")\r
+public class Group implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+  private String groupName;\r
+  private String groupDescription;\r
+  private List<ObjectId> mechanizedIds;\r
+  private ObjectId ownerId;\r
+  private List<Role> roles;\r
+  private List<GroupMember> members;\r
+  private ObjectId parentGroupId;\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getGroupName() {\r
+    return groupName;\r
+  }\r
+\r
+  public void setGroupName(String groupName) {\r
+    this.groupName = groupName;\r
+  }\r
+\r
+  public String getGroupDescription() {\r
+    return groupDescription;\r
+  }\r
+\r
+  public void setGroupDescription(String groupDescription) {\r
+    this.groupDescription = groupDescription;\r
+  }\r
+\r
+  public List<ObjectId> getMechanizedIds() {\r
+    return mechanizedIds;\r
+  }\r
+\r
+  public void setMechanizedIds(List<ObjectId> mechanizedIds) {\r
+    this.mechanizedIds = mechanizedIds;\r
+  }\r
+\r
+  public ObjectId getOwnerId() {\r
+    return ownerId;\r
+  }\r
+\r
+  public void setOwnerId(ObjectId ownerId) {\r
+    this.ownerId = ownerId;\r
+  }\r
+\r
+  public List<Role> getRoles() {\r
+    return roles;\r
+  }\r
+\r
+  public void setRoles(List<Role> roles) {\r
+    this.roles = roles;\r
+  }\r
+\r
+  public List<GroupMember> getMembers() {\r
+    return members;\r
+  }\r
+\r
+  public void setMembers(List<GroupMember> members) {\r
+    this.members = members;\r
+  }\r
+\r
+  public ObjectId getParentGroupId() {\r
+    return parentGroupId;\r
+  }\r
+\r
+  public void setParentGroupId(ObjectId parentGroupId) {\r
+    this.parentGroupId = parentGroupId;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/GroupMember.java b/otf-camunda/src/main/java/org/oran/otf/common/model/GroupMember.java
new file mode 100644 (file)
index 0000000..6ab79c2
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.bson.types.ObjectId;\r
+\r
+import java.util.List;\r
+\r
+public class GroupMember {\r
+    private ObjectId userId;\r
+    private List<String> roles;//this is name of roles assigned to user that are created within the group i.e admin,dev,.. etc\r
+\r
+    public ObjectId getUserId() {\r
+        return userId;\r
+    }\r
+\r
+    public void setUserId(ObjectId userId) {\r
+        this.userId = userId;\r
+    }\r
+\r
+    public List<String> getRoles() {\r
+        return roles;\r
+    }\r
+\r
+    public void setRoles(List<String> roles) {\r
+        this.roles = roles;\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/Role.java b/otf-camunda/src/main/java/org/oran/otf/common/model/Role.java
new file mode 100644 (file)
index 0000000..aca09f1
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import java.util.List;\r
+\r
+public class Role {\r
+\r
+    private String roleName;\r
+    private List<String> permissions;\r
+\r
+    public String getRoleName() {\r
+        return roleName;\r
+    }\r
+\r
+    public void setRoleName(String roleName) {\r
+        this.roleName = roleName;\r
+    }\r
+\r
+    public List<String> getPermissions() {\r
+        return permissions;\r
+    }\r
+\r
+    public void setPermissions(List<String> permissions) {\r
+        this.permissions = permissions;\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/TestDefinition.java b/otf-camunda/src/main/java/org/oran/otf/common/model/TestDefinition.java
new file mode 100644 (file)
index 0000000..b59a746
--- /dev/null
@@ -0,0 +1,138 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testDefinitions")\r
+public class TestDefinition implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+  private String testName;\r
+  private String testDescription;\r
+  private String processDefinitionKey;\r
+  private List<BpmnInstance> bpmnInstances;\r
+  private ObjectId groupId;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+  private boolean disabled;\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestName() {\r
+    return testName;\r
+  }\r
+\r
+  public void setTestName(String testName) {\r
+    this.testName = testName;\r
+  }\r
+\r
+  public String getTestDescription() {\r
+    return testDescription;\r
+  }\r
+\r
+  public void setTestDescription(String testDescription) {\r
+    this.testDescription = testDescription;\r
+  }\r
+\r
+  public String getProcessDefinitionKey() {\r
+    return processDefinitionKey;\r
+  }\r
+\r
+  public void setProcessDefinitionKey(String processDefinitionKey) {\r
+    this.processDefinitionKey = processDefinitionKey;\r
+  }\r
+\r
+  public List<BpmnInstance> getBpmnInstances() {\r
+    return bpmnInstances;\r
+  }\r
+\r
+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {\r
+    this.bpmnInstances = bpmnInstances;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public boolean isDisabled() {\r
+    return disabled;\r
+  }\r
+\r
+  public void setDisabled(boolean disabled) {\r
+    this.disabled = disabled;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/TestExecution.java b/otf-camunda/src/main/java/org/oran/otf/common/model/TestExecution.java
new file mode 100644 (file)
index 0000000..85b4a71
--- /dev/null
@@ -0,0 +1,235 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.historic.TestDefinitionHistoric;\r
+import org.oran.otf.common.model.historic.TestInstanceHistoric;\r
+import org.oran.otf.common.model.local.TestHeadResult;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testExecutions")\r
+public class TestExecution implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+  private ObjectId groupId;\r
+  private ObjectId executorId;\r
+\r
+  private boolean async;\r
+  private Date startTime;\r
+  private Date endTime;\r
+  private String businessKey;\r
+  private String processInstanceId;\r
+  private String testResult;\r
+  private String testResultMessage;\r
+  private Map<String, Object> testDetails;\r
+  private List<TestHeadResult> testHeadResults;\r
+  private List<TestExecution> testInstanceResults;\r
+  // Stores historic information of associated\r
+  private String historicEmail;\r
+  private TestInstanceHistoric historicTestInstance;\r
+  private TestDefinitionHistoric historicTestDefinition;\r
+\r
+  public TestExecution() {\r
+  }\r
+\r
+  public TestExecution(\r
+      ObjectId _id,\r
+      ObjectId groupId,\r
+      ObjectId executorId,\r
+      boolean async,\r
+      Date startTime,\r
+      Date endTime,\r
+      String businessKey,\r
+      String processInstanceId,\r
+      String testResult,\r
+      String testResultMessage,\r
+      Map<String, Object> testDetails,\r
+      List<TestHeadResult> testHeadResults,\r
+      List<TestExecution> testInstanceResults,\r
+      String historicEmail,\r
+      TestInstanceHistoric historicTestInstance,\r
+      TestDefinitionHistoric historicTestDefinition) {\r
+    this._id = _id;\r
+    this.groupId = groupId;\r
+    this.executorId = executorId;\r
+    this.async = async;\r
+    this.startTime = startTime;\r
+    this.endTime = endTime;\r
+    this.businessKey = businessKey;\r
+    this.processInstanceId = processInstanceId;\r
+    this.testResult = testResult;\r
+    this.testResultMessage = testResultMessage;\r
+    this.testDetails = testDetails;\r
+    this.testHeadResults = testHeadResults;\r
+    this.testInstanceResults = testInstanceResults;\r
+    this.historicEmail = historicEmail;\r
+    this.historicTestInstance = historicTestInstance;\r
+    this.historicTestDefinition = historicTestDefinition;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public ObjectId getExecutorId() {\r
+    return executorId;\r
+  }\r
+\r
+  public void setExecutorId(ObjectId executorId) {\r
+    this.executorId = executorId;\r
+  }\r
+\r
+  public boolean isAsync() {\r
+    return async;\r
+  }\r
+\r
+  public void setAsync(boolean async) {\r
+    this.async = async;\r
+  }\r
+\r
+  public Date getStartTime() {\r
+    return startTime;\r
+  }\r
+\r
+  public void setStartTime(Date startTime) {\r
+    this.startTime = startTime;\r
+  }\r
+\r
+  public Date getEndTime() {\r
+    return endTime;\r
+  }\r
+\r
+  public void setEndTime(Date endTime) {\r
+    this.endTime = endTime;\r
+  }\r
+\r
+  public String getBusinessKey() {\r
+    return businessKey;\r
+  }\r
+\r
+  public void setBusinessKey(String businessKey) {\r
+    this.businessKey = businessKey;\r
+  }\r
+\r
+  public String getProcessInstanceId() {\r
+    return processInstanceId;\r
+  }\r
+\r
+  public void setProcessInstanceId(String processInstanceId) {\r
+    this.processInstanceId = processInstanceId;\r
+  }\r
+\r
+  public String getTestResult() {\r
+    return testResult;\r
+  }\r
+\r
+  public void setTestResult(String testResult) {\r
+    this.testResult = testResult;\r
+  }\r
+\r
+  public String getTestResultMessage() {\r
+    return testResultMessage;\r
+  }\r
+\r
+  public void setTestResultMessage(String testResultMessage) {\r
+    this.testResultMessage = testResultMessage;\r
+  }\r
+\r
+  public Map<String, Object> getTestDetails() {\r
+    return testDetails;\r
+  }\r
+\r
+  public void setTestDetails(Map<String, Object> testDetails) {\r
+    this.testDetails = testDetails;\r
+  }\r
+\r
+  public List<TestHeadResult> getTestHeadResults() {\r
+    synchronized (testHeadResults) {\r
+      return testHeadResults;\r
+    }\r
+  }\r
+\r
+  public void setTestHeadResults(List<TestHeadResult> testHeadResults) {\r
+    synchronized (testHeadResults) {\r
+      this.testHeadResults = testHeadResults;\r
+    }\r
+  }\r
+\r
+  public List<TestExecution> getTestInstanceResults() {\r
+    synchronized (testInstanceResults) {\r
+      return testInstanceResults;\r
+    }\r
+  }\r
+\r
+  public void setTestInstanceResults(List<TestExecution> testInstanceResults) {\r
+    synchronized (testInstanceResults) {\r
+      this.testInstanceResults = testInstanceResults;\r
+    }\r
+  }\r
+\r
+  public String getHistoricEmail() {\r
+    return historicEmail;\r
+  }\r
+\r
+  public void setHistoricEmail(String historicEmail) {\r
+    this.historicEmail = historicEmail;\r
+  }\r
+\r
+  public TestInstanceHistoric getHistoricTestInstance() {\r
+    return historicTestInstance;\r
+  }\r
+\r
+  public void setHistoricTestInstance(TestInstanceHistoric historicTestInstance) {\r
+    this.historicTestInstance = historicTestInstance;\r
+  }\r
+\r
+  public TestDefinitionHistoric getHistoricTestDefinition() {\r
+    return historicTestDefinition;\r
+  }\r
+\r
+  public void setHistoricTestDefinition(\r
+      TestDefinitionHistoric historicTestDefinition) {\r
+    this.historicTestDefinition = historicTestDefinition;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/TestHead.java b/otf-camunda/src/main/java/org/oran/otf/common/model/TestHead.java
new file mode 100644 (file)
index 0000000..7f4bcbc
--- /dev/null
@@ -0,0 +1,224 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.Map;\r
+\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.index.Indexed;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testHeads")\r
+public class TestHead implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+\r
+  @Indexed(unique = true)\r
+  private String testHeadName;\r
+\r
+  private String testHeadDescription;\r
+  private String hostname;\r
+  private String port;\r
+  private String resourcePath;\r
+  private ObjectId creatorId;\r
+  private ObjectId groupId;\r
+  private String authorizationType;\r
+  private String authorizationCredential;\r
+  private Boolean authorizationEnabled;\r
+  private Map<String, Object> vthInputTemplate;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId updatedBy;\r
+  private Boolean isPublic;\r
+  public TestHead() {\r
+  }\r
+\r
+  public TestHead(\r
+          ObjectId _id,\r
+          String testHeadName,\r
+          String testHeadDescription,\r
+          String hostname,\r
+          String port,\r
+          String resourcePath,\r
+          ObjectId creatorId,\r
+          ObjectId groupId,\r
+          String authorizationType,\r
+          String authorizationCredential,\r
+          boolean authorizationEnabled,\r
+          Map<String, Object> vthInputTemplate,\r
+          Date createdAt,\r
+          Date updatedAt,\r
+          ObjectId updatedBy,\r
+          Boolean isPublic) {\r
+    this._id = _id;\r
+    this.testHeadName = testHeadName;\r
+    this.testHeadDescription = testHeadDescription;\r
+    this.hostname = hostname;\r
+    this.port = port;\r
+    this.resourcePath = resourcePath;\r
+    this.creatorId = creatorId;\r
+    this.groupId = groupId;\r
+    this.authorizationType = authorizationType;\r
+    this.authorizationCredential = authorizationCredential;\r
+    this.authorizationEnabled = authorizationEnabled;\r
+    this.vthInputTemplate = vthInputTemplate;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.updatedBy = updatedBy;\r
+    this.isPublic = isPublic;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestHeadName() {\r
+    return testHeadName;\r
+  }\r
+\r
+  public void setTestHeadName(String testHeadName) {\r
+    this.testHeadName = testHeadName;\r
+  }\r
+\r
+  public String getTestHeadDescription() {\r
+    return testHeadDescription;\r
+  }\r
+\r
+  public void setTestHeadDescription(String testHeadDescription) {\r
+    this.testHeadDescription = testHeadDescription;\r
+  }\r
+\r
+  public String getHostname() {\r
+    return hostname;\r
+  }\r
+\r
+  public void setHostname(String hostname) {\r
+    this.hostname = hostname;\r
+  }\r
+\r
+  public String getPort() {\r
+    return port;\r
+  }\r
+\r
+  public void setPort(String port) {\r
+    this.port = port;\r
+  }\r
+\r
+  public String getResourcePath() {\r
+    return resourcePath;\r
+  }\r
+\r
+  public void setResourcePath(String resourcePath) {\r
+    this.resourcePath = resourcePath;\r
+  }\r
+\r
+  public ObjectId getCreatorId() {\r
+    return creatorId;\r
+  }\r
+\r
+  public void setCreatorId(ObjectId creatorId) {\r
+    this.creatorId = creatorId;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public String getAuthorizationCredential() {\r
+    return authorizationCredential;\r
+  }\r
+\r
+  public String getAuthorizationType() {\r
+    return authorizationType;\r
+  }\r
+\r
+  public void setAuthorizationType(String authorizationType) {\r
+    this.authorizationType = authorizationType;\r
+  }\r
+\r
+  public void setAuthorizationCredential(String authorizationCredential) {\r
+    this.authorizationCredential = authorizationCredential;\r
+  }\r
+\r
+  public Boolean getAuthorizationEnabled() {\r
+    return authorizationEnabled;\r
+  }\r
+\r
+  public void setAuthorizationEnabled(Boolean authorizationEnabled) {\r
+    this.authorizationEnabled = authorizationEnabled;\r
+  }\r
+\r
+  public Map<String, Object> getVthInputTemplate() {\r
+    return vthInputTemplate;\r
+  }\r
+\r
+  public void setVthInputTemplate(Map<String, Object> vthInputTemplate) {\r
+    this.vthInputTemplate = vthInputTemplate;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public Boolean isPublic() {\r
+    return isPublic;\r
+  }\r
+\r
+  public void setPublic(Boolean aPublic) {\r
+    isPublic = aPublic;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/TestInstance.java b/otf-camunda/src/main/java/org/oran/otf/common/model/TestInstance.java
new file mode 100644 (file)
index 0000000..96fcfa9
--- /dev/null
@@ -0,0 +1,259 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testInstances")\r
+public class TestInstance implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private @Id\r
+  ObjectId _id;\r
+  private String testInstanceName;\r
+  private String testInstanceDescription;\r
+  private ObjectId groupId;\r
+  private ObjectId testDefinitionId;\r
+  private String processDefinitionId;\r
+  private boolean useLatestTestDefinition;\r
+  private boolean disabled;\r
+  private boolean simulationMode;\r
+  private long maxExecutionTimeInMillis;\r
+  private Map<String, ParallelFlowInput> pfloInput;\r
+  private Map<String, Object> internalTestData;\r
+  private Map<String, Object> simulationVthInput;\r
+  private Map<String, Object> testData;\r
+  private Map<String, Object> vthInput;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+\r
+  public TestInstance() {\r
+  }\r
+\r
+  public TestInstance(\r
+      ObjectId _id,\r
+      String testInstanceName,\r
+      String testInstanceDescription,\r
+      ObjectId groupId,\r
+      ObjectId testDefinitionId,\r
+      String processDefinitionId,\r
+      boolean useLatestTestDefinition,\r
+      boolean disabled,\r
+      boolean simulationMode,\r
+      long maxExecutionTimeInMillis,\r
+      HashMap<String, ParallelFlowInput> pfloInput,\r
+      HashMap<String, Object> internalTestData,\r
+      HashMap<String, Object> simulationVthInput,\r
+      HashMap<String, Object> testData,\r
+      HashMap<String, Object> vthInput,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy) {\r
+    this._id = _id;\r
+    this.testInstanceName = testInstanceName;\r
+    this.testInstanceDescription = testInstanceDescription;\r
+    this.groupId = groupId;\r
+    this.testDefinitionId = testDefinitionId;\r
+    this.processDefinitionId = processDefinitionId;\r
+    this.useLatestTestDefinition = useLatestTestDefinition;\r
+    this.disabled = disabled;\r
+    this.simulationMode = simulationMode;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+    this.pfloInput = pfloInput;\r
+    this.internalTestData = internalTestData;\r
+    this.simulationVthInput = simulationVthInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestInstanceName() {\r
+    return testInstanceName;\r
+  }\r
+\r
+  public void setTestInstanceName(String testInstanceName) {\r
+    this.testInstanceName = testInstanceName;\r
+  }\r
+\r
+  public String getTestInstanceDescription() {\r
+    return testInstanceDescription;\r
+  }\r
+\r
+  public void setTestInstanceDescription(String testInstanceDescription) {\r
+    this.testInstanceDescription = testInstanceDescription;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionId() {\r
+    return testDefinitionId;\r
+  }\r
+\r
+  public void setTestDefinitionId(ObjectId testDefinitionId) {\r
+    this.testDefinitionId = testDefinitionId;\r
+  }\r
+\r
+  public String getProcessDefinitionId() {\r
+    return processDefinitionId;\r
+  }\r
+\r
+  public void setProcessDefinitionId(String processDefinitionId) {\r
+    this.processDefinitionId = processDefinitionId;\r
+  }\r
+\r
+  public boolean isUseLatestTestDefinition() {\r
+    return useLatestTestDefinition;\r
+  }\r
+\r
+  public void setUseLatestTestDefinition(boolean useLatestTestDefinition) {\r
+    this.useLatestTestDefinition = useLatestTestDefinition;\r
+  }\r
+\r
+  public boolean isDisabled() {\r
+    return disabled;\r
+  }\r
+\r
+  public void setDisabled(boolean disabled) {\r
+    this.disabled = disabled;\r
+  }\r
+\r
+  public boolean isSimulationMode() {\r
+    return simulationMode;\r
+  }\r
+\r
+  public void setSimulationMode(boolean simulationMode) {\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  public long getMaxExecutionTimeInMillis() {\r
+    return maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public Map<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(HashMap<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public Map<String, Object> getInternalTestData() {\r
+    return internalTestData;\r
+  }\r
+\r
+  public void setInternalTestData(HashMap<String, Object> internalTestData) {\r
+    this.internalTestData = internalTestData;\r
+  }\r
+\r
+  public Map<String, Object> getSimulationVthInput() {\r
+    return simulationVthInput;\r
+  }\r
+\r
+  public void setSimulationVthInput(HashMap<String, Object> simulationVthInput) {\r
+    this.simulationVthInput = simulationVthInput;\r
+  }\r
+\r
+  public Map<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(HashMap<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public Map<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(HashMap<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/User.java b/otf-camunda/src/main/java/org/oran/otf/common/model/User.java
new file mode 100644 (file)
index 0000000..9262b52
--- /dev/null
@@ -0,0 +1,139 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.local.UserGroup;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "users")\r
+public class User implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId _id;\r
+  private List<String> permissions;\r
+  private String firstName;\r
+  private String lastName;\r
+  private String email;\r
+  private String password;\r
+  private List<UserGroup> groups;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+\r
+  public User(\r
+      ObjectId _id,\r
+      List<String> permissions,\r
+      String firstName,\r
+      String lastName,\r
+      String email,\r
+      String password,\r
+      List<UserGroup> groups,\r
+      Date createdAt,\r
+      Date updatedAt) {\r
+    this._id = _id;\r
+    this.permissions = permissions;\r
+    this.firstName = firstName;\r
+    this.lastName = lastName;\r
+    this.email = email;\r
+    this.password = password;\r
+    this.groups = groups;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public List<String> getPermissions() {\r
+    return permissions;\r
+  }\r
+\r
+  public void setPermissions(List<String> permissions) {\r
+    this.permissions = permissions;\r
+  }\r
+\r
+  public String getFirstName() {\r
+    return firstName;\r
+  }\r
+\r
+  public void setFirstName(String firstName) {\r
+    this.firstName = firstName;\r
+  }\r
+\r
+  public String getLastName() {\r
+    return lastName;\r
+  }\r
+\r
+  public void setLastName(String lastName) {\r
+    this.lastName = lastName;\r
+  }\r
+\r
+  public String getEmail() {\r
+    return email;\r
+  }\r
+\r
+  public void setEmail(String email) {\r
+    this.email = email;\r
+  }\r
+\r
+  public String getPassword() {\r
+    return password;\r
+  }\r
+\r
+  public void setPassword(String password) {\r
+    this.password = password;\r
+  }\r
+\r
+  public List<UserGroup> getGroups() {\r
+    return groups;\r
+  }\r
+\r
+  public void setGroups(List<UserGroup> groups) {\r
+    this.groups = groups;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java b/otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java
new file mode 100644 (file)
index 0000000..aa32b5b
--- /dev/null
@@ -0,0 +1,188 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.historic;\r
+\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.Date;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+\r
+public class TestDefinitionHistoric implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId _id;\r
+  private String testName;\r
+  private String testDescription;\r
+  private String processDefinitionKey;\r
+  private List<BpmnInstance> bpmnInstances;\r
+  private ObjectId groupId;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+\r
+  public TestDefinitionHistoric() {\r
+  }\r
+\r
+  public TestDefinitionHistoric(TestDefinition testDefinition, String processDefinitionId) {\r
+    this._id = testDefinition.get_id();\r
+    this.testName = testDefinition.getTestName();\r
+    this.testDescription = testDefinition.getTestDescription();\r
+    this.processDefinitionKey = testDefinition.getProcessDefinitionKey();\r
+    this.bpmnInstances =\r
+        getHistoricBpmnInstanceAsList(testDefinition.getBpmnInstances(), processDefinitionId);\r
+    this.groupId = testDefinition.getGroupId();\r
+    this.createdAt = testDefinition.getCreatedAt();\r
+    this.updatedAt = testDefinition.getUpdatedAt();\r
+    this.createdBy = testDefinition.getCreatedBy();\r
+    this.updatedBy = testDefinition.getUpdatedBy();\r
+  }\r
+\r
+  public TestDefinitionHistoric(\r
+      ObjectId _id,\r
+      String testName,\r
+      String testDescription,\r
+      String processDefinitionKey,\r
+      List<BpmnInstance> bpmnInstances,\r
+      ObjectId groupId,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy) {\r
+    this._id = _id;\r
+    this.testName = testName;\r
+    this.testDescription = testDescription;\r
+    this.processDefinitionKey = processDefinitionKey;\r
+    this.bpmnInstances = bpmnInstances;\r
+    this.groupId = groupId;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  private List<BpmnInstance> getHistoricBpmnInstanceAsList(\r
+      List<BpmnInstance> bpmnInstances, String processDefinitionId) {\r
+    BpmnInstance bpmnInstance =\r
+        bpmnInstances.stream()\r
+            .filter(\r
+                _bpmnInstance -> {\r
+                  return _bpmnInstance.isDeployed()\r
+                      && _bpmnInstance.getProcessDefinitionId() != null\r
+                      && _bpmnInstance.getProcessDefinitionId().equals(processDefinitionId);\r
+                })\r
+            .findFirst()\r
+            .orElse(null);\r
+\r
+    List<BpmnInstance> historicBpmnInstance = new ArrayList<>();\r
+    if (bpmnInstance != null) {\r
+      historicBpmnInstance.add(bpmnInstance);\r
+    }\r
+\r
+    return historicBpmnInstance;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestName() {\r
+    return testName;\r
+  }\r
+\r
+  public void setTestName(String testName) {\r
+    this.testName = testName;\r
+  }\r
+\r
+  public String getTestDescription() {\r
+    return testDescription;\r
+  }\r
+\r
+  public void setTestDescription(String testDescription) {\r
+    this.testDescription = testDescription;\r
+  }\r
+\r
+  public String getProcessDefinitionKey() {\r
+    return processDefinitionKey;\r
+  }\r
+\r
+  public void setProcessDefinitionKey(String processDefinitionKey) {\r
+    this.processDefinitionKey = processDefinitionKey;\r
+  }\r
+\r
+  public List<BpmnInstance> getBpmnInstances() {\r
+    return bpmnInstances;\r
+  }\r
+\r
+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {\r
+    this.bpmnInstances = bpmnInstances;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java b/otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java
new file mode 100644 (file)
index 0000000..1263893
--- /dev/null
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.historic;\r
+\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+\r
+public class TestInstanceHistoric implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private @Id\r
+  ObjectId _id;\r
+  private String testInstanceName;\r
+  private String testInstanceDescription;\r
+  private ObjectId groupId;\r
+  private ObjectId testDefinitionId;\r
+  private String processDefinitionId;\r
+  private Map<String, ParallelFlowInput> pfloInput;\r
+  private Map<String, Object> simulationVthInput;\r
+  private Map<String, Object> testData;\r
+  private Map<String, Object> vthInput;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+  private boolean simulationMode;\r
+\r
+  public TestInstanceHistoric() {\r
+  }\r
+\r
+  public TestInstanceHistoric(TestInstance testInstance) {\r
+    this._id = testInstance.get_id();\r
+    this.testInstanceName = testInstance.getTestInstanceName();\r
+    this.testInstanceDescription = testInstance.getTestInstanceDescription();\r
+    this.groupId = testInstance.getGroupId();\r
+    this.testDefinitionId = testInstance.getTestDefinitionId();\r
+    this.pfloInput = testInstance.getPfloInput();\r
+    this.processDefinitionId = testInstance.getProcessDefinitionId();\r
+    this.simulationVthInput = testInstance.getSimulationVthInput();\r
+    this.testData = testInstance.getTestData();\r
+    this.vthInput = testInstance.getVthInput();\r
+    this.createdAt = testInstance.getCreatedAt();\r
+    this.updatedAt = testInstance.getUpdatedAt();\r
+    this.createdBy = testInstance.getCreatedBy();\r
+    this.updatedBy = testInstance.getUpdatedBy();\r
+    this.simulationMode = testInstance.isSimulationMode();\r
+  }\r
+\r
+  public TestInstanceHistoric(\r
+      ObjectId _id,\r
+      String testInstanceName,\r
+      String testInstanceDescription,\r
+      ObjectId groupId,\r
+      ObjectId testDefinitionId,\r
+      String processDefinitionId,\r
+      HashMap<String, ParallelFlowInput> pfloInput,\r
+      HashMap<String, Object> simulationVthInput,\r
+      HashMap<String, Object> testData,\r
+      HashMap<String, Object> vthInput,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy,\r
+      boolean simulationMode) {\r
+    this._id = _id;\r
+    this.testInstanceName = testInstanceName;\r
+    this.testInstanceDescription = testInstanceDescription;\r
+    this.groupId = groupId;\r
+    this.testDefinitionId = testDefinitionId;\r
+    this.processDefinitionId = processDefinitionId;\r
+    this.pfloInput = pfloInput;\r
+    this.simulationVthInput = simulationVthInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestInstanceName() {\r
+    return testInstanceName;\r
+  }\r
+\r
+  public void setTestInstanceName(String testInstanceName) {\r
+    this.testInstanceName = testInstanceName;\r
+  }\r
+\r
+  public String getTestInstanceDescription() {\r
+    return testInstanceDescription;\r
+  }\r
+\r
+  public void setTestInstanceDescription(String testInstanceDescription) {\r
+    this.testInstanceDescription = testInstanceDescription;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionId() {\r
+    return testDefinitionId;\r
+  }\r
+\r
+  public void setTestDefinitionId(ObjectId testDefinitionId) {\r
+    this.testDefinitionId = testDefinitionId;\r
+  }\r
+\r
+  public String getProcessDefinitionId() {\r
+    return processDefinitionId;\r
+  }\r
+\r
+  public void setProcessDefinitionId(String processDefinitionId) {\r
+    this.processDefinitionId = processDefinitionId;\r
+  }\r
+\r
+  public Map<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(\r
+      HashMap<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public Map<String, Object> getSimulationVthInput() {\r
+    return simulationVthInput;\r
+  }\r
+\r
+  public void setSimulationVthInput(\r
+      HashMap<String, Object> simulationVthInput) {\r
+    this.simulationVthInput = simulationVthInput;\r
+  }\r
+\r
+  public Map<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(HashMap<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public Map<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(HashMap<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public boolean isSimulationMode() {\r
+    return simulationMode;\r
+  }\r
+\r
+  public void setSimulationMode(boolean simulationMode) {\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java
new file mode 100644 (file)
index 0000000..580e86c
--- /dev/null
@@ -0,0 +1,187 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+public class BpmnInstance implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private String processDefinitionId;\r
+  private String deploymentId;\r
+  private int version;\r
+  private ObjectId bpmnFileId;\r
+  private ObjectId resourceFileId;\r
+  private boolean isDeployed;\r
+  private List<TestHeadNode> testHeads;\r
+  private List<PfloNode> pflos;\r
+  private Map<String, Object> testDataTemplate;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+\r
+  public BpmnInstance() {\r
+  }\r
+\r
+  public BpmnInstance(\r
+      String processDefinitionId,\r
+      String deploymentId,\r
+      int version,\r
+      ObjectId bpmnFileId,\r
+      ObjectId resourceFileId,\r
+      boolean isDeployed,\r
+      List<TestHeadNode> testHeads,\r
+      List<PfloNode> pflos,\r
+      Map<String, Object> testDataTemplate,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy) {\r
+    this.processDefinitionId = processDefinitionId;\r
+    this.deploymentId = deploymentId;\r
+    this.version = version;\r
+    this.bpmnFileId = bpmnFileId;\r
+    this.resourceFileId = resourceFileId;\r
+    this.isDeployed = isDeployed;\r
+    this.testHeads = testHeads;\r
+    this.testDataTemplate = testDataTemplate;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public String getProcessDefinitionId() {\r
+    return processDefinitionId;\r
+  }\r
+\r
+  public void setProcessDefinitionId(String processDefinitionId) {\r
+    this.processDefinitionId = processDefinitionId;\r
+  }\r
+\r
+  public String getDeploymentId() {\r
+    return deploymentId;\r
+  }\r
+\r
+  public void setDeploymentId(String deploymentId) {\r
+    this.deploymentId = deploymentId;\r
+  }\r
+\r
+  public int getVersion() {\r
+    return version;\r
+  }\r
+\r
+  public void setVersion(int version) {\r
+    this.version = version;\r
+  }\r
+\r
+  public ObjectId getBpmnFileId() {\r
+    return bpmnFileId;\r
+  }\r
+\r
+  public void setBpmnFileId(ObjectId bpmnFileId) {\r
+    this.bpmnFileId = bpmnFileId;\r
+  }\r
+\r
+  public ObjectId getResourceFileId() {\r
+    return resourceFileId;\r
+  }\r
+\r
+  public void setResourceFileId(ObjectId resourceFileId) {\r
+    this.resourceFileId = resourceFileId;\r
+  }\r
+\r
+  public boolean isDeployed() {\r
+    return isDeployed;\r
+  }\r
+\r
+  public void setDeployed(boolean deployed) {\r
+    isDeployed = deployed;\r
+  }\r
+\r
+  public List<TestHeadNode> getTestHeads() {\r
+    return testHeads;\r
+  }\r
+\r
+  public void setTestHeads(List<TestHeadNode> testHeads) {\r
+    this.testHeads = testHeads;\r
+  }\r
+\r
+  public List<PfloNode> getPflos() {\r
+    return pflos;\r
+  }\r
+\r
+  public void setPflos(List<PfloNode> pflos) {\r
+    this.pflos = pflos;\r
+  }\r
+\r
+  public Map<String, Object> getTestDataTemplate() {\r
+    return testDataTemplate;\r
+  }\r
+\r
+  public void setTestDataTemplate(Map<String, Object> testDataTemplate) {\r
+    this.testDataTemplate = testDataTemplate;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  private String getObjectIdString(ObjectId value) {\r
+    return value == null ? "\"\"" : "\"" + value.toString() + "\"";\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/DMaaPRequest.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/DMaaPRequest.java
new file mode 100644 (file)
index 0000000..e294d44
--- /dev/null
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import com.fasterxml.jackson.annotation.JsonProperty;\r
+\r
+public class DMaaPRequest {\r
+    private String hostname;\r
+    private String asyncTopic;\r
+    private boolean requiresProxy;\r
+\r
+    public DMaaPRequest() {\r
+    }\r
+\r
+    public DMaaPRequest(\r
+            @JsonProperty(value = "hostname", required = true) String hostname,\r
+            @JsonProperty(value = "asyncTopic", required = true) String asyncTopic,\r
+            @JsonProperty(value = "requriesProxy", required = false) boolean requiresProxy) {\r
+        this.hostname = hostname;\r
+        this.asyncTopic = asyncTopic;\r
+        this.requiresProxy = requiresProxy;\r
+\r
+    }\r
+\r
+    public String getHostname() {\r
+        return hostname;\r
+    }\r
+\r
+    public void setHostname(String hostname) {\r
+        this.hostname = hostname;\r
+    }\r
+\r
+    public String getAsyncTopic() {\r
+        return asyncTopic;\r
+    }\r
+\r
+    public void setAsyncTopic(String asyncTopic) {\r
+        this.asyncTopic = asyncTopic;\r
+    }\r
+\r
+    public boolean getRequiresProxy(){\r
+        return this.requiresProxy;\r
+    }\r
+\r
+    public void setRequiresProxy(boolean requiresProxy) {\r
+        this.requiresProxy = requiresProxy;\r
+    }\r
+\r
+    @Override\r
+    public String toString() {\r
+        return Convert.objectToJson(this);\r
+    }\r
+}\r
+\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java
new file mode 100644 (file)
index 0000000..cd8ed5a
--- /dev/null
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+\r
+import java.util.Date;\r
+\r
+public class OTFApiResponse {\r
+\r
+  private int statusCode;\r
+  private String message;\r
+  private Date time;\r
+\r
+  public OTFApiResponse() {\r
+  }\r
+\r
+  public OTFApiResponse(int statusCode, String message) {\r
+    this.statusCode = statusCode;\r
+    this.message = message;\r
+    this.time = new Date(System.currentTimeMillis());\r
+  }\r
+\r
+  public int getStatusCode() {\r
+    return statusCode;\r
+  }\r
+\r
+  public void setStatusCode(int statusCode) {\r
+    this.statusCode = statusCode;\r
+  }\r
+\r
+  public String getMessage() {\r
+    return message;\r
+  }\r
+\r
+  public void setMessage(String message) {\r
+    this.message = message;\r
+  }\r
+\r
+  public Date getTime() {\r
+    return time;\r
+  }\r
+\r
+  public void setTime(Date time) {\r
+    this.time = time;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFDeploymentResponse.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFDeploymentResponse.java
new file mode 100644 (file)
index 0000000..59c3dcd
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+public class OTFDeploymentResponse {\r
+\r
+    private String deploymentId;\r
+    private String processDefinitionKey;\r
+    private String processDefinitionId;\r
+    private int version;\r
+\r
+    public OTFDeploymentResponse() {\r
+    }\r
+\r
+    public OTFDeploymentResponse(String deploymentId, String processDefinitionKey, String processDefinitionId, int version) {\r
+        this.deploymentId = deploymentId;\r
+        this.processDefinitionKey = processDefinitionKey;\r
+        this.processDefinitionId = processDefinitionId;\r
+        this.version = version;\r
+    }\r
+\r
+    public String getDeploymentId() {\r
+        return deploymentId;\r
+    }\r
+\r
+    public void setDeploymentId(String deploymentId) {\r
+        this.deploymentId = deploymentId;\r
+    }\r
+\r
+    public String getProcessDefinitionKey() {\r
+        return processDefinitionKey;\r
+    }\r
+\r
+    public void setProcessDefinitionKey(String processDefinitionKey) {\r
+        this.processDefinitionKey = processDefinitionKey;\r
+    }\r
+\r
+    public String getProcessDefinitionId() {\r
+        return processDefinitionId;\r
+    }\r
+\r
+    public void setProcessDefinitionId(String processDefinitionId) {\r
+        this.processDefinitionId = processDefinitionId;\r
+    }\r
+\r
+    public int getVersion() {\r
+        return version;\r
+    }\r
+\r
+    public void setVersion(int version) {\r
+        this.version = version;\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFProcessInstanceCompletionResponse.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFProcessInstanceCompletionResponse.java
new file mode 100644 (file)
index 0000000..dd1ecf6
--- /dev/null
@@ -0,0 +1,166 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.camunda.model.ExecutionConstants;\r
+import org.camunda.bpm.engine.history.*;\r
+import org.camunda.bpm.engine.impl.history.event.HistoricExternalTaskLogEntity;\r
+import org.camunda.bpm.engine.impl.persistence.entity.HistoricJobLogEventEntity;\r
+import org.camunda.bpm.engine.impl.persistence.entity.HistoricVariableInstanceEntity;\r
+\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+public class OTFProcessInstanceCompletionResponse implements Serializable {\r
+    private HistoricProcessInstance historicProcessInstance;\r
+    private List<HistoricActivityInstance> historicActivityInstance;\r
+    private List<HistoricIncident> historicIncident;\r
+    private List<Map<String, Object>> historicJobLog;\r
+    private List<Map<String, Object>> historicExternalTaskLog;\r
+    private List<Map<String, Object>> historicVariableInstance;\r
+\r
+    public OTFProcessInstanceCompletionResponse() {\r
+    }\r
+\r
+\r
+    public HistoricProcessInstance getHistoricProcessInstance() {\r
+        return historicProcessInstance;\r
+    }\r
+\r
+    public void setHistoricProcessInstance(HistoricProcessInstance historicProcessInstance) {\r
+        this.historicProcessInstance = historicProcessInstance;\r
+    }\r
+\r
+    public List<HistoricActivityInstance> getHistoricActivityInstance() {\r
+        return historicActivityInstance;\r
+    }\r
+\r
+    public void setHistoricActivityInstance(List<HistoricActivityInstance> historicActivityInstance) {\r
+        this.historicActivityInstance = historicActivityInstance;\r
+    }\r
+\r
+    public List<HistoricIncident> getHistoricIncident() {\r
+        return historicIncident;\r
+    }\r
+\r
+    public void setHistoricIncident(List<HistoricIncident> historicIncident) {\r
+        this.historicIncident = historicIncident;\r
+    }\r
+\r
+    public List<Map<String, Object>> getHistoricJobLog() {\r
+        return historicJobLog;\r
+    }\r
+\r
+    public void setHistoricJobLog(List<HistoricJobLog> historicJobLog) {\r
+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();\r
+        for(HistoricJobLog jobLog: historicJobLog){\r
+            HistoricJobLogEventEntity log = (HistoricJobLogEventEntity) jobLog;\r
+            HashMap map = new HashMap();\r
+\r
+            map.put("id", log.getId());\r
+            map.put("executionId", log.getExecutionId());\r
+            map.put("activityId", log.getActivityId());\r
+            map.put("eventType", log.getEventType());\r
+            map.put("sequenceCounter", log.getSequenceCounter());\r
+            map.put("retries", log.getJobRetries());\r
+            map.put("jobExceptionMessage", log.getJobExceptionMessage());\r
+            map.put("jobDefinitionType", log.getJobDefinitionType());\r
+            map.put("jobDefinitionConfiguration", log.getJobDefinitionConfiguration());\r
+            map.put("processDefinitionKey", log.getProcessDefinitionKey());\r
+            map.put("state", convertState(log.getState()));\r
+\r
+            list.add(map);\r
+        }\r
+        this.historicJobLog = list;\r
+    }\r
+\r
+    public List<Map<String, Object>> getHistoricExternalTaskLog() {\r
+        return this.historicExternalTaskLog;\r
+    }\r
+\r
+    public void setHistoricExternalTaskLog(List<HistoricExternalTaskLog> historicExternalTaskLog) {\r
+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();\r
+        for(HistoricExternalTaskLog externalTaskLog: historicExternalTaskLog){\r
+            HistoricExternalTaskLogEntity log = (HistoricExternalTaskLogEntity) externalTaskLog;\r
+            HashMap map = new HashMap();\r
+\r
+            map.put("id", log.getId());\r
+            map.put("executionId", log.getExecutionId());\r
+            map.put("activityId", log.getActivityId());\r
+            map.put("state", convertState(log.getState()));\r
+            map.put("retries", log.getRetries());\r
+            map.put("processDefinitionKey", log.getProcessDefinitionKey());\r
+            map.put("errorMessage", log.getErrorMessage());\r
+            try {\r
+                map.put("errorDetails", log.getErrorDetails());\r
+            }\r
+            catch (Exception e){}\r
+            map.put("workerId", log.getWorkerId());\r
+            map.put("topic", log.getTopicName());\r
+            list.add(map);\r
+        }\r
+        this.historicExternalTaskLog = list;\r
+    }\r
+\r
+    public List<Map<String, Object>> getHistoricVariableInstance() {\r
+        return historicVariableInstance;\r
+    }\r
+\r
+    public void setHistoricVariableInstance(List<HistoricVariableInstance> historicVariableInstance) {\r
+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();\r
+        for(HistoricVariableInstance variableInstanceEntity: historicVariableInstance){\r
+            HistoricVariableInstanceEntity variable = (HistoricVariableInstanceEntity) variableInstanceEntity;\r
+            HashMap map = new HashMap();\r
+            if (variable.getVariableName().equalsIgnoreCase(ExecutionConstants.ExecutionVariable.TEST_EXECUTION)){\r
+                continue;\r
+            }\r
+            map.put("id", variable.getId());\r
+            map.put("executionId", variable.getExecutionId());\r
+            map.put("processDefinitionKey", variable.getProcessDefinitionKey());\r
+            map.put("taskId", variable.getTaskId());\r
+            map.put("eventType", variable.getVariableName());\r
+            map.put("errorMessage", variable.getErrorMessage());\r
+            map.put("state", variable.getState());\r
+            map.put("variableName", variable.getVariableName());\r
+            map.put("type",  variable.getTypedValue().getType());\r
+            map.put("value",  variable.getTypedValue().getValue());\r
+            map.put("persistentState", variable.getPersistentState());\r
+\r
+            list.add(map);\r
+        }\r
+        this.historicVariableInstance = list;\r
+    }\r
+\r
+    private String convertState(int state){\r
+        switch (state){\r
+            case 0 :\r
+                return "CREATED";\r
+            case 1 :\r
+                return "FAILED";\r
+            case 2 :\r
+                return "SUCCESSFUL";\r
+            case 3 :\r
+                return "DELETED";\r
+            default:\r
+                return "UNKNOWN";\r
+        }\r
+    }\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java
new file mode 100644 (file)
index 0000000..62ee1c8
--- /dev/null
@@ -0,0 +1,91 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.camunda.workflow.WorkflowRequest;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import com.fasterxml.jackson.annotation.JsonCreator;\r
+import com.fasterxml.jackson.annotation.JsonProperty;\r
+\r
+import java.io.Serializable;\r
+import java.util.List;\r
+\r
+public class ParallelFlowInput implements Serializable {\r
+\r
+    private static final long serialVersionUID = 1L;\r
+\r
+    private List<WorkflowRequest> args;\r
+    private boolean interruptOnFailure;\r
+    private int maxFailures;\r
+    private int threadPoolSize;\r
+\r
+    public ParallelFlowInput() {\r
+    }\r
+\r
+    @JsonCreator\r
+    public ParallelFlowInput(\r
+        @JsonProperty(value = "args", required = true) List<WorkflowRequest> args,\r
+        @JsonProperty(value = "interruptOnFailure", required = true) boolean interruptOnFailure,\r
+        @JsonProperty(value = "maxFailures", required = true) int maxFailures,\r
+        @JsonProperty(value = "threadPoolSize", required = true) int threadPoolSize) {\r
+        this.args = args;\r
+        this.interruptOnFailure = interruptOnFailure;\r
+        this.maxFailures = maxFailures;\r
+        this.threadPoolSize = threadPoolSize;\r
+    }\r
+\r
+    public static long getSerialVersionUID() {\r
+        return serialVersionUID;\r
+    }\r
+\r
+    public List<WorkflowRequest> getArgs() {\r
+        return args;\r
+    }\r
+\r
+    public void setArgs(List<WorkflowRequest> args) {\r
+        this.args = args;\r
+    }\r
+\r
+    public boolean isInterruptOnFailure() {\r
+        return interruptOnFailure;\r
+    }\r
+\r
+    public void setInterruptOnFailure(boolean interruptOnFailure) {\r
+        this.interruptOnFailure = interruptOnFailure;\r
+    }\r
+\r
+    public int getMaxFailures() {\r
+        return maxFailures;\r
+    }\r
+\r
+    public void setMaxFailures(int maxFailures) {\r
+        this.maxFailures = maxFailures;\r
+    }\r
+\r
+    public int getThreadPoolSize() {\r
+        return threadPoolSize;\r
+    }\r
+\r
+    public void setThreadPoolSize(int threadPoolSize) {\r
+        this.threadPoolSize = threadPoolSize;\r
+    }\r
+\r
+    @Override\r
+    public String toString() {\r
+        return Convert.objectToJson(this);\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/PfloNode.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/PfloNode.java
new file mode 100644 (file)
index 0000000..f70b8a6
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+\r
+import java.io.Serializable;\r
+\r
+public class PfloNode implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private String bpmnPlfoTaskId;\r
+  private String label;\r
+\r
+  public PfloNode() {\r
+  }\r
+\r
+  public PfloNode(String bpmnPlfoTaskId, String label) {\r
+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;\r
+    this.label = label;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public String getBpmnPlfoTaskId() {\r
+    return bpmnPlfoTaskId;\r
+  }\r
+\r
+  public void setBpmnPlfoTaskId(String bpmnPlfoTaskId) {\r
+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;\r
+  }\r
+\r
+  public String getLabel() {\r
+    return label;\r
+  }\r
+\r
+  public void setLabel(String label) {\r
+    this.label = label;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java
new file mode 100644 (file)
index 0000000..99ed995
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import org.bson.types.ObjectId;\r
+\r
+public class TestHeadNode implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId testHeadId;\r
+  private String bpmnVthTaskId;\r
+\r
+  public TestHeadNode() {\r
+  }\r
+\r
+  public TestHeadNode(ObjectId testHeadId, String taskId) {\r
+    this.testHeadId = testHeadId;\r
+    this.bpmnVthTaskId = taskId;\r
+  }\r
+\r
+  public ObjectId getTestHeadId() {\r
+    return testHeadId;\r
+  }\r
+\r
+  public void setTestHeadId(ObjectId testHeadId) {\r
+    this.testHeadId = testHeadId;\r
+  }\r
+\r
+  public String getBpmnVthTaskId() {\r
+    return bpmnVthTaskId;\r
+  }\r
+\r
+  public void setBpmnVthTaskId(String bpmnVthTaskId) {\r
+    this.bpmnVthTaskId = bpmnVthTaskId;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java
new file mode 100644 (file)
index 0000000..5bebd80
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import java.io.Serializable;\r
+import java.util.Map;\r
+\r
+public class TestHeadRequest implements Serializable {\r
+  private static final long serialVersionUID = 1L;\r
+  private Map<String, String> headers;\r
+  private Map<String, Object>  body;\r
+\r
+  public TestHeadRequest(){}\r
+\r
+  public TestHeadRequest(Map<String, String> headers,\r
+      Map<String, Object> body) {\r
+    this.headers = headers;\r
+    this.body = body;\r
+  }\r
+\r
+  public Map<String, String> getHeaders() {\r
+    return headers;\r
+  }\r
+\r
+  public void setHeaders(Map<String, String> headers) {\r
+    this.headers = headers;\r
+  }\r
+\r
+  public Map<String, Object> getBody() {\r
+    return body;\r
+  }\r
+\r
+  public void setBody(Map<String, Object> body) {\r
+    this.body = body;\r
+  }\r
+\r
+\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java
new file mode 100644 (file)
index 0000000..33746c1
--- /dev/null
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+public class TestHeadResult implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId testHeadId;\r
+  private String testHeadName;\r
+  private ObjectId testHeadGroupId;\r
+  private String bpmnVthTaskId;\r
+\r
+  //TODO: RG Remove maps below, setters and getters to return to normal\r
+  //private Map<String, String> testHeadHeaders;\r
+  //private int testHeadCode;\r
+  private int statusCode;\r
+\r
+  private TestHeadRequest testHeadRequest;\r
+  private Map<String, Object> testHeadResponse;\r
+  private Date startTime;\r
+  private Date endTime;\r
+\r
+  public TestHeadResult() {\r
+  }\r
+\r
+  public TestHeadResult(\r
+      ObjectId testHeadId,\r
+      String testHeadName,\r
+      ObjectId testHeadGroupId,\r
+      String bpmnVthTaskId,\r
+\r
+      //TODO: RG changed code to int and changed testHeadRequest from Map<String, String> to RequestContent\r
+      int statusCode,\r
+\r
+      TestHeadRequest testHeadRequest,\r
+      Map<String, Object> testHeadResponse,\r
+      Date startTime,\r
+      Date endTime) {\r
+    this.testHeadId = testHeadId;\r
+    this.testHeadName = testHeadName;\r
+    this.testHeadGroupId = testHeadGroupId;\r
+    this.bpmnVthTaskId = bpmnVthTaskId;\r
+\r
+    //this.testHeadHeaders = testHeadHeaders;\r
+    this.statusCode = statusCode;\r
+\r
+    this.testHeadRequest = testHeadRequest;\r
+    this.testHeadResponse = testHeadResponse;\r
+    this.startTime = startTime;\r
+    this.endTime = endTime;\r
+  }\r
+\r
+  public int getStatusCode(){return statusCode;}\r
+  public void setStatusCode(int testHeadCode){this.statusCode = statusCode;}\r
+\r
+  public ObjectId getTestHeadId() {\r
+    return testHeadId;\r
+  }\r
+\r
+  public void setTestHeadId(ObjectId testHeadId) {\r
+    this.testHeadId = testHeadId;\r
+  }\r
+\r
+  public String getTestHeadName() {\r
+    return testHeadName;\r
+  }\r
+\r
+  public void setTestHeadName(String testHeadName) {\r
+    this.testHeadName = testHeadName;\r
+  }\r
+\r
+  public ObjectId getTestHeadGroupId() {\r
+    return testHeadGroupId;\r
+  }\r
+\r
+  public void setTestHeadGroupId(ObjectId testHeadGroupId) {\r
+    this.testHeadGroupId = testHeadGroupId;\r
+  }\r
+\r
+  public String getBpmnVthTaskId() {\r
+    return bpmnVthTaskId;\r
+  }\r
+\r
+  public void setBpmnVthTaskId(String bpmnVthTaskId) {\r
+    this.bpmnVthTaskId = bpmnVthTaskId;\r
+  }\r
+\r
+  public TestHeadRequest getTestHeadRequest() {\r
+    return testHeadRequest;\r
+  }\r
+\r
+  public void setTestHeadRequest(TestHeadRequest testHeadRequest) {\r
+    this.testHeadRequest = testHeadRequest;\r
+  }\r
+\r
+  public Map<String, Object> getTestHeadResponse() {\r
+    return testHeadResponse;\r
+  }\r
+\r
+  public void setTestHeadResponse(Map<String, Object> testHeadResponse) {\r
+    this.testHeadResponse = testHeadResponse;\r
+  }\r
+\r
+  public Date getStartTime() {\r
+    return startTime;\r
+  }\r
+\r
+  public void setStartTime(Date startTime) {\r
+    this.startTime = startTime;\r
+  }\r
+\r
+  public Date getEndTime() {\r
+    return endTime;\r
+  }\r
+\r
+  public void setEndTime(Date endTime) {\r
+    this.endTime = endTime;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/model/local/UserGroup.java b/otf-camunda/src/main/java/org/oran/otf/common/model/local/UserGroup.java
new file mode 100644 (file)
index 0000000..b103f1e
--- /dev/null
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+\r
+public class UserGroup implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId groupId;\r
+  private List<String> permissions;\r
+\r
+  public UserGroup(ObjectId groupId, List<String> permissions) {\r
+    this.groupId = groupId;\r
+    this.permissions = permissions;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public List<String> getPermissions() {\r
+    return permissions;\r
+  }\r
+\r
+  public void setPermissions(List<String> permissions) {\r
+    this.permissions = permissions;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/repository/GroupRepository.java b/otf-camunda/src/main/java/org/oran/otf/common/repository/GroupRepository.java
new file mode 100644 (file)
index 0000000..1876651
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+import org.springframework.data.mongodb.repository.Query;\r
+\r
+import java.util.List;\r
+\r
+public interface GroupRepository extends MongoRepository<Group, String> {\r
+    @Query("{ 'members.userId': ?0 }")\r
+    public List<Group> findAllByMembersId(ObjectId membersUserId);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java
new file mode 100644 (file)
index 0000000..fa89a93
--- /dev/null
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestDefinition;\r
+import java.util.Optional;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface TestDefinitionRepository extends MongoRepository<TestDefinition, String> {\r
+  Optional<TestDefinition> findByProcessDefinitionKey(String processDefinitionKey);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java
new file mode 100644 (file)
index 0000000..5122fea
--- /dev/null
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import java.util.Optional;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface TestExecutionRepository extends MongoRepository<TestExecution, String> {\r
+  Optional<TestExecution> findFirstByProcessInstanceId(String processInstanceId);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java
new file mode 100644 (file)
index 0000000..126d00f
--- /dev/null
@@ -0,0 +1,24 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestHead;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface TestHeadRepository extends MongoRepository<TestHead, String> {\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java
new file mode 100644 (file)
index 0000000..b073155
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestInstance;\r
+import java.util.List;\r
+import java.util.Optional;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+import org.springframework.data.mongodb.repository.Query;\r
+\r
+public interface TestInstanceRepository extends MongoRepository<TestInstance, String> {\r
+  Optional<TestInstance> findByTestInstanceName(String testInstanceName);\r
+\r
+  @Query("{ 'testDefinitionId': ?0 }")\r
+  List<TestInstance> findAllByTestDefinitionId(ObjectId testDefinitionId);\r
+\r
+  @Query("{ 'testDefinitionId': ?0, 'processDefinitionId': ?1 }")\r
+  List<TestInstance> findAllByTestDefinitionIdAndPDId(\r
+      ObjectId testDefinitionId, String processDefinitionId);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/repository/UserRepository.java b/otf-camunda/src/main/java/org/oran/otf/common/repository/UserRepository.java
new file mode 100644 (file)
index 0000000..5dd669f
--- /dev/null
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.User;\r
+import java.util.Optional;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface UserRepository extends MongoRepository<User, String> {\r
+  Optional<User> findFirstByEmail(String email);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/Utility.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/Utility.java
new file mode 100644 (file)
index 0000000..5d7798b
--- /dev/null
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility;\r
+\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import com.google.common.base.Strings;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.UUID;\r
+\r
+public class Utility {\r
+\r
+  public static String getLoggerPrefix() {\r
+    return "[" + Thread.currentThread().getStackTrace()[2].getMethodName() + "]: ";\r
+  }\r
+\r
+  public static Map<?, ?> toMap(Object obj) throws Exception {\r
+    ObjectMapper mapper = new ObjectMapper();\r
+    return mapper.convertValue(obj, HashMap.class);\r
+  }\r
+\r
+  public static boolean isCollection(Object obj) {\r
+    return obj.getClass().isArray() || obj instanceof Collection;\r
+  }\r
+\r
+  public static List<?> toList(Object obj) {\r
+    if (obj == null) {\r
+      throw new NullPointerException("Argument cannot be null.");\r
+    }\r
+\r
+    List<?> list = new ArrayList<>();\r
+    if (obj.getClass().isArray()) {\r
+      list = Arrays.asList((Object[]) obj);\r
+    } else if (obj instanceof Collection) {\r
+      list = new ArrayList<>((Collection<?>) obj);\r
+    }\r
+\r
+    return list;\r
+  }\r
+\r
+  public static boolean isValidUuid(String str) {\r
+    if (Strings.isNullOrEmpty(str)) {\r
+      return false;\r
+    }\r
+    try {\r
+      UUID uuid = UUID.fromString(str);\r
+      return uuid.toString().equalsIgnoreCase(str);\r
+    } catch (IllegalArgumentException iae) {\r
+      return false;\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/database/Generic.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/database/Generic.java
new file mode 100644 (file)
index 0000000..5de5043
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.database;\r
+\r
+import java.util.Optional;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public class Generic {\r
+\r
+  public static <T> boolean identifierExistsInCollection(\r
+      MongoRepository<T, String> repository, ObjectId identifier) {\r
+    return repository.findById(identifier.toString()).isPresent();\r
+  }\r
+\r
+  public static <T> T findByIdGeneric(MongoRepository<T, String> repository, ObjectId identifier) {\r
+    Optional<T> optionalObj = repository.findById(identifier.toString());\r
+    return optionalObj.orElse(null);\r
+  }\r
+\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java
new file mode 100644 (file)
index 0000000..ba236cb
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.database;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import com.mongodb.client.result.UpdateResult;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+\r
+public class TestExecutionUtility {\r
+\r
+  public static void saveTestResult(MongoTemplate mongoOperation, TestExecution execution, String testResult){\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("businessKey").is(execution.getBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testResult", testResult);\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/gson/Convert.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/gson/Convert.java
new file mode 100644 (file)
index 0000000..fb1e55f
--- /dev/null
@@ -0,0 +1,121 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.gson;\r
+\r
+import com.fasterxml.jackson.core.type.TypeReference;\r
+import com.fasterxml.jackson.databind.DeserializationFeature;\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import com.google.gson.Gson;\r
+import com.google.gson.GsonBuilder;\r
+import com.google.gson.JsonDeserializationContext;\r
+import com.google.gson.JsonDeserializer;\r
+import com.google.gson.JsonElement;\r
+import com.google.gson.JsonParseException;\r
+import com.google.gson.JsonPrimitive;\r
+import com.google.gson.JsonSerializationContext;\r
+import com.google.gson.JsonSerializer;\r
+import com.google.gson.reflect.TypeToken;\r
+\r
+import java.io.IOException;\r
+import java.lang.reflect.Type;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+public class Convert {\r
+\r
+  private static final GsonBuilder gsonBuilder =\r
+      new GsonBuilder()\r
+          .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")\r
+          .registerTypeAdapter(\r
+              ObjectId.class,\r
+              new JsonSerializer<ObjectId>() {\r
+                @Override\r
+                public JsonElement serialize(\r
+                    ObjectId src, Type typeOfSrc, JsonSerializationContext context) {\r
+                  return new JsonPrimitive(src.toHexString());\r
+                }\r
+              })\r
+          .registerTypeAdapter(\r
+              ObjectId.class,\r
+              new JsonDeserializer<ObjectId>() {\r
+                @Override\r
+                public ObjectId deserialize(\r
+                    JsonElement json, Type typeOfT, JsonDeserializationContext context)\r
+                    throws JsonParseException {\r
+                  return new ObjectId(json.getAsString());\r
+                }\r
+              });\r
+\r
+  public static Gson getGson() {\r
+    return gsonBuilder.create();\r
+  }\r
+\r
+  public static String mapToJson(Map map) {\r
+    if (map.isEmpty()) {\r
+      return "{}";\r
+    }\r
+    return getGson().toJson(map);\r
+  }\r
+\r
+  public static String listToJson(List list) {\r
+    if (list.isEmpty()) {\r
+      return "[]";\r
+    }\r
+    return getGson().toJson(list);\r
+  }\r
+\r
+  public static Map<String, Object> jsonToMap(String json) {\r
+    Type type = new TypeToken<HashMap<String, Object>>() {\r
+    }.getType();\r
+    return getGson().fromJson(json, type);\r
+  }\r
+\r
+  public static String objectToJson(Object obj) {\r
+    return getGson().toJson(obj);\r
+  }\r
+\r
+  public static <T> T jsonToObject(String json, TypeReference<T> typeReference) throws IOException {\r
+    ObjectMapper objectMapper = new ObjectMapper();\r
+    objectMapper\r
+            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);\r
+    return objectMapper.readValue(json, typeReference);\r
+  }\r
+\r
+  public static<T> T mapToObject(Map map, TypeReference<T> typeReference) throws IOException {\r
+    return jsonToObject(mapToJson(map), typeReference);\r
+  }\r
+\r
+  public static<T> T listToObjectList(List list, TypeReference<T> typeReference) throws IOException {\r
+    return jsonToObject(listToJson(list), typeReference);\r
+  }\r
+\r
+  public static List<?> convertObjectToList(Object obj) {\r
+    List<?> list = new ArrayList<>();\r
+    if (obj.getClass().isArray()) {\r
+      list = Arrays.asList((Object[])obj);\r
+    } else if (obj instanceof Collection) {\r
+      list = new ArrayList<>((Collection<?>)obj);\r
+    }\r
+    return list;\r
+  }\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/http/HeadersUtility.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/http/HeadersUtility.java
new file mode 100644 (file)
index 0000000..be6ae88
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.http;\r
+\r
+import com.google.gson.Gson;\r
+import java.util.Map;\r
+\r
+public class HeadersUtility {\r
+  public static Map<String, String> maskAuth(Map<String, String> headers){\r
+    //Deep copy headers to avoid changing original\r
+    Gson gson = new Gson();\r
+    String jsonString = gson.toJson(headers);\r
+    Map<String, String> maskedHeaders = gson.fromJson(jsonString, Map.class);\r
+\r
+    if(maskedHeaders.containsKey("Authorization")) {\r
+      String[] auth = maskedHeaders.get("Authorization").split(" ");\r
+      if(auth.length>1) {\r
+        auth[1] = "****";\r
+        maskedHeaders.put("Authorization", auth[0] + " " + auth[1]);\r
+      }\r
+      else{\r
+        maskedHeaders.put("Authorization", "****");\r
+      }\r
+    }\r
+    return maskedHeaders;\r
+  }\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java
new file mode 100644 (file)
index 0000000..8cd2eff
--- /dev/null
@@ -0,0 +1,195 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.http;\r
+\r
+import com.google.common.base.Strings;\r
+import java.io.UnsupportedEncodingException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+import java.util.concurrent.Future;\r
+import org.apache.http.HttpHost;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.client.config.RequestConfig;\r
+import org.apache.http.client.methods.HttpGet;\r
+import org.apache.http.client.methods.HttpPost;\r
+import org.apache.http.client.methods.HttpRequestBase;\r
+import org.apache.http.client.protocol.HttpClientContext;\r
+import org.apache.http.entity.StringEntity;\r
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;\r
+import org.apache.http.impl.nio.client.HttpAsyncClients;\r
+import org.apache.http.protocol.BasicHttpContext;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+\r
+public class RequestUtility {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(RequestUtility.class);\r
+\r
+  public static void postAsync(String url, String body, Map<String, String> headers, Boolean proxy)\r
+      throws Exception {\r
+    HttpPost post = buildPost(url, body, headers);\r
+    executeAsync(post, proxy);\r
+  }\r
+\r
+  public static HttpResponse postSync(\r
+      String url, String body, Map<String, String> headers, Boolean proxy) throws Exception {\r
+    HttpPost post = buildPost(url, body, headers);\r
+    return executeSync(post, proxy);\r
+  }\r
+\r
+  public static HttpResponse postSync(\r
+      String url, String body, Map<String, String> headers, int timeoutInMillis, Boolean proxy)\r
+      throws Exception {\r
+    HttpPost post = buildPost(url, body, headers);\r
+    return executeSync(post, timeoutInMillis, proxy);\r
+  }\r
+\r
+  public static HttpResponse getSync(String url, Map<String, String> headers, Boolean proxy)\r
+      throws Exception {\r
+    HttpGet get = buildGet(url, headers);\r
+    return executeSync(get, proxy);\r
+  }\r
+\r
+  public static HttpResponse getSync(\r
+      String url, Map<String, String> headers, int timeoutInMillis, Boolean proxy)\r
+      throws Exception {\r
+    if (timeoutInMillis < 0) {\r
+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");\r
+    }\r
+\r
+    HttpGet get = buildGet(url, headers);\r
+    return executeSync(get, timeoutInMillis, proxy);\r
+  }\r
+\r
+  public static void getAsync(String url, Map<String, String> headers, Boolean proxy)\r
+      throws Exception {\r
+    HttpGet get = buildGet(url, headers);\r
+    executeAsync(get, proxy);\r
+  }\r
+\r
+  private static HttpPost buildPost(String url, String body, Map<String, String> headers)\r
+      throws UnsupportedEncodingException {\r
+    if (Strings.isNullOrEmpty(url) || Strings.isNullOrEmpty(body)) {\r
+      return null;\r
+    } else if (headers == null) {\r
+      headers = new HashMap<>();\r
+    }\r
+\r
+    HttpPost post = new HttpPost(url);\r
+    headers.forEach(post::setHeader);\r
+    post.setEntity(new StringEntity(body));\r
+    return post;\r
+  }\r
+\r
+  private static HttpGet buildGet(String url, Map<String, String> headers) {\r
+    if (Strings.isNullOrEmpty(url)) {\r
+      return null;\r
+    } else if (headers == null) {\r
+      headers = new HashMap<>();\r
+    }\r
+\r
+    HttpGet get = new HttpGet(url);\r
+    headers.forEach(get::setHeader);\r
+    return get;\r
+  }\r
+\r
+  private static HttpResponse executeSync(HttpRequestBase request, Boolean proxy) throws Exception {\r
+    CloseableHttpAsyncClient httpClient = createHttpAsyncClient();\r
+    try {\r
+      httpClient.start();\r
+      Future<HttpResponse> future =\r
+          proxy\r
+              ? httpClient.execute(request, createHttpClientContext(), null)\r
+              : httpClient.execute(request, null);\r
+      return future.get();\r
+    } catch (Exception e) {\r
+      throw e;\r
+    } finally {\r
+      httpClient.close();\r
+    }\r
+  }\r
+\r
+  private static HttpResponse executeSync(\r
+      HttpRequestBase request, int timeoutInMillis, Boolean proxy) throws Exception {\r
+    if (timeoutInMillis < 0) {\r
+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");\r
+    }\r
+\r
+    // Create a timer task that will abort the task (the request) after the specified time. This\r
+    // task will run *timeoutInMillis* ms\r
+    TimerTask task =\r
+        new TimerTask() {\r
+          @Override\r
+          public void run() {\r
+            if (request != null) {\r
+              request.abort();\r
+            }\r
+          }\r
+        };\r
+\r
+    CloseableHttpAsyncClient httpClient = createHttpAsyncClient();\r
+    try {\r
+      httpClient.start();\r
+      // Start the timer before making the request.\r
+      new Timer(true).schedule(task, timeoutInMillis);\r
+      Future<HttpResponse> future =\r
+          proxy\r
+              ? httpClient.execute(request, createHttpClientContext(), null)\r
+              : httpClient.execute(request, null);\r
+\r
+      return future.get();\r
+    } catch (Exception e) {\r
+      throw e;\r
+    } finally {\r
+      httpClient.close();\r
+    }\r
+  }\r
+\r
+  private static void executeAsync(HttpRequestBase request, Boolean proxy) throws Exception {\r
+    CloseableHttpAsyncClient httpClient = createHttpAsyncClient();\r
+    try {\r
+      httpClient.start();\r
+      Future<HttpResponse> future =\r
+          proxy\r
+              ? httpClient.execute(request, createHttpClientContext(), null)\r
+              : httpClient.execute(request, null);\r
+      logger.debug("Sent asynchronous request.");\r
+    } catch (Exception e) {\r
+      throw e;\r
+    } finally {\r
+      httpClient.close();\r
+    }\r
+  }\r
+\r
+  private static RequestConfig configureProxy() {\r
+    HttpHost proxy;\r
+    proxy = new HttpHost("localhost", 8080, "http");\r
+    return RequestConfig.custom().setProxy(proxy).build();\r
+  }\r
+\r
+  private static HttpClientContext createHttpClientContext() {\r
+    HttpClientContext localContext = HttpClientContext.adapt(new BasicHttpContext());\r
+    localContext.setRequestConfig(configureProxy());\r
+    return localContext;\r
+  }\r
+\r
+  private static CloseableHttpAsyncClient createHttpAsyncClient() throws Exception {\r
+    return HttpAsyncClients.createDefault();\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java
new file mode 100644 (file)
index 0000000..abe1e0b
--- /dev/null
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.http;\r
+\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+\r
+public class ResponseUtility {\r
+\r
+  public static class Build {\r
+\r
+    public static Response okRequest() {\r
+      return Response.ok().build();\r
+    }\r
+\r
+    public static Response badRequest() {\r
+      return Response.status(400).build();\r
+    }\r
+\r
+    public static Response okRequestWithMessage(String msg) {\r
+      return Response.status(200)\r
+              .type(MediaType.APPLICATION_JSON)\r
+              .entity(new OTFApiResponse(200, msg))\r
+              .build();\r
+    }\r
+\r
+    public static Response okRequestWithObject(Object obj) {\r
+      return Response.status(200)\r
+              .type(MediaType.APPLICATION_JSON)\r
+              .entity(obj)\r
+              .build();\r
+    }\r
+\r
+    public static Response badRequestWithMessage(String msg) {\r
+      return Response.status(400)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(400, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response internalServerError() {\r
+      return Response.status(500).build();\r
+    }\r
+\r
+    public static Response internalServerErrorWithMessage(String msg) {\r
+      return Response.status(500)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(500, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response unauthorized() {\r
+      return Response.status(401).build();\r
+    }\r
+\r
+    public static Response unauthorizedWithMessage(String msg) {\r
+      return Response.status(401)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(401, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response notFound() {\r
+      return Response.status(404).build();\r
+    }\r
+\r
+    public static Response notFoundWithMessage(String msg) {\r
+      return Response.status(404)\r
+              .type(MediaType.APPLICATION_JSON)\r
+              .entity(new OTFApiResponse(404, msg))\r
+              .build();\r
+    }\r
+\r
+    public static Response genericWithCode(int code) {\r
+      return Response.status(code).build();\r
+    }\r
+\r
+    public static Response genericWithMessage(int code, String msg) {\r
+      return Response.status(code)\r
+              .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(code, msg))\r
+              .build();\r
+    }\r
+\r
+    public static Response genericWithObject(int code, Object obj) {\r
+      return Response.status(code)\r
+              .type(MediaType.APPLICATION_JSON)\r
+              .entity(obj)\r
+              .build();\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java
new file mode 100644 (file)
index 0000000..8327a81
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.logger;\r
+\r
+public enum ErrorCode {\r
+  PermissionError(100),\r
+  AvailabilityError(200),\r
+  DataError(300),\r
+  SchemaError(400),\r
+  BusinessProcesssError(500),\r
+  UnknownError(900);\r
+\r
+  private int value;\r
+\r
+  ErrorCode(int value) {\r
+    this.value = value;\r
+  }\r
+\r
+  public int getValue() {\r
+    return this.value;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java
new file mode 100644 (file)
index 0000000..10c45d8
--- /dev/null
@@ -0,0 +1,91 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * ================================================================================\r
+ * Modifications Copyright (c) 2019 Samsung\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.common.utility.logger;\r
+\r
+import ch.qos.logback.classic.Level;\r
+import ch.qos.logback.classic.LoggerContext;\r
+import ch.qos.logback.classic.spi.LoggerContextListener;\r
+import ch.qos.logback.core.Context;\r
+import ch.qos.logback.core.spi.ContextAwareBase;\r
+import ch.qos.logback.core.spi.LifeCycle;\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class LoggerStartupListener extends ContextAwareBase\r
+    implements LoggerContextListener, LifeCycle {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(LoggerStartupListener.class);\r
+  private boolean started = false;\r
+\r
+  @Override\r
+  public void start() {\r
+    if (started) {\r
+      return;\r
+    }\r
+    InetAddress addr = null;\r
+    try {\r
+      addr = InetAddress.getLocalHost();\r
+    } catch (UnknownHostException e) {\r
+      logger.error("UnknownHostException", e);\r
+    }\r
+    Context context = getContext();\r
+    if (addr != null) {\r
+      context.putProperty("server.name", addr.getHostName());\r
+    }\r
+    started = true;\r
+  }\r
+\r
+  @Override\r
+  public void stop() {\r
+  }\r
+\r
+  @Override\r
+  public boolean isStarted() {\r
+    return started;\r
+  }\r
+\r
+  @Override\r
+  public boolean isResetResistant() {\r
+    return true;\r
+  }\r
+\r
+  @Override\r
+  public void onReset(LoggerContext arg0) {\r
+  }\r
+\r
+  @Override\r
+  public void onStart(LoggerContext arg0) {\r
+  }\r
+\r
+  @Override\r
+  public void onStop(LoggerContext arg0) {\r
+  }\r
+\r
+  @Override\r
+  public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java
new file mode 100644 (file)
index 0000000..1103c53
--- /dev/null
@@ -0,0 +1,35 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.common.utility.logger;\r
+\r
+\r
+public enum MessageEnum {\r
+  // Api Handler Messages\r
+  APIH_REQUEST_NULL, APIH_QUERY_FOUND, APIH_QUERY_NOT_FOUND, APIH_QUERY_PARAM_WRONG, APIH_DB_ACCESS_EXC, APIH_DB_ACCESS_EXC_REASON, APIH_VALIDATION_ERROR, APIH_REQUEST_VALIDATION_ERROR, APIH_SERVICE_VALIDATION_ERROR, APIH_GENERAL_EXCEPTION_ARG, APIH_GENERAL_EXCEPTION, APIH_GENERAL_WARNING, APIH_AUDIT_EXEC, APIH_GENERAL_METRICS, APIH_DUPLICATE_CHECK_EXC, APIH_DUPLICATE_FOUND, APIH_BAD_ORDER, APIH_DB_ATTRIBUTE_NOT_FOUND, APIH_BPEL_COMMUNICATE_ERROR, APIH_BPEL_RESPONSE_ERROR, APIH_WARP_REQUEST, APIH_ERROR_FROM_BPEL_SERVER, APIH_DB_INSERT_EXC, APIH_DB_UPDATE_EXC, APIH_NO_PROPERTIES, APIH_PROPERTY_LOAD_SUC, APIH_LOAD_PROPERTIES_FAIL, APIH_SDNC_COMMUNICATE_ERROR, APIH_SDNC_RESPONSE_ERROR, APIH_CANNOT_READ_SCHEMA, APIH_HEALTH_CHECK_EXCEPTION, APIH_REQUEST_VALIDATION_ERROR_REASON, APIH_JAXB_MARSH_ERROR, APIH_JAXB_UNMARSH_ERROR, APIH_VNFREQUEST_VALIDATION_ERROR, APIH_DOM2STR_ERROR, APIH_READ_VNFOUTPUT_CLOB_EXCEPTION, APIH_DUPLICATE_CHECK_EXC_ATT, APIH_GENERATED_REQUEST_ID, APIH_GENERATED_SERVICE_INSTANCE_ID, APIH_REPLACE_REQUEST_ID,\r
+  // Resource Adapter Messages\r
+  RA_GENERAL_EXCEPTION_ARG, RA_GENERAL_EXCEPTION, RA_GENERAL_WARNING, RA_MISSING_PARAM, RA_AUDIT_EXEC, RA_GENERAL_METRICS, RA_CREATE_STACK_TIMEOUT, RA_DELETE_STACK_TIMEOUT, RA_UPDATE_STACK_TIMEOUT, RA_CONNECTION_EXCEPTION, RA_PARSING_ERROR, RA_PROPERTIES_NOT_FOUND, RA_LOAD_PROPERTIES_SUC, RA_NETWORK_ALREADY_EXIST, RA_UPDATE_NETWORK_ERR, RA_CREATE_STACK_ERR, RA_UPDATE_STACK_ERR, RA_CREATE_TENANT_ERR, RA_NETWORK_NOT_FOUND, RA_NETWORK_ORCHE_MODE_NOT_SUPPORT, RA_CREATE_NETWORK_EXC, RA_NS_EXC, RA_PARAM_NOT_FOUND, RA_CONFIG_EXC, RA_UNKOWN_PARAM, RA_VLAN_PARSE, RA_DELETE_NETWORK_EXC, RA_ROLLBACK_NULL, RA_TENANT_NOT_FOUND, RA_QUERY_NETWORK_EXC, RA_CREATE_NETWORK_NOTIF_EXC, RA_ASYNC_ROLLBACK, RA_WSDL_NOT_FOUND, RA_WSDL_URL_CONVENTION_EXC, RA_INIT_NOTIF_EXC, RA_SET_CALLBACK_AUTH_EXC, RA_FAULT_INFO_EXC, RA_MARSHING_ERROR, RA_PARSING_REQUEST_ERROR, RA_SEND_REQUEST_SDNC, RA_RESPONSE_FROM_SDNC, RA_EXCEPTION_COMMUNICATE_SDNC, RA_EVALUATE_XPATH_ERROR, RA_ANALYZE_ERROR_EXC, RA_ERROR_GET_RESPONSE_SDNC, RA_CALLBACK_BPEL, RA_INIT_CALLBACK_WSDL_ERR, RA_CALLBACK_BPEL_EXC, RA_CALLBACK_BPEL_COMPLETE, RA_SDNC_MISS_CONFIG_PARAM, RA_SDNC_INVALID_CONFIG, RA_PRINT_URL, RA_ERROR_CREATE_SDNC_REQUEST, RA_ERROR_CREATE_SDNC_RESPONSE, RA_ERROR_CONVERT_XML2STR, RA_RECEIVE_SDNC_NOTIF, RA_INIT_SDNC_ADAPTER, RA_SEND_REQUEST_APPC_ERR, RA_SEND_REQUEST_SDNC_ERR, RA_RECEIVE_BPEL_REQUEST, RA_TENANT_ALREADY_EXIST, RA_UPDATE_TENANT_ERR, RA_DELETE_TEMAMT_ERR, RA_ROLLBACK_TENANT_ERR, RA_QUERY_VNF_ERR, RA_VNF_ALREADY_EXIST, RA_VNF_UNKNOWN_PARAM, RA_VNF_EXTRA_PARAM, RA_CREATE_VNF_ERR, RA_VNF_NOT_EXIST, RA_UPDATE_VNF_ERR, RA_DELETE_VNF_ERR, RA_ASYNC_CREATE_VNF, RA_SEND_VNF_NOTIF_ERR, RA_ASYNC_CREATE_VNF_COMPLETE, RA_ASYNC_UPDATE_VNF, RA_ASYNC_UPDATE_VNF_COMPLETE, RA_ASYNC_QUERY_VNF, RA_ASYNC_QUERY_VNF_COMPLETE, RA_ASYNC_DELETE_VNF, RA_ASYNC_DELETE_VNF_COMPLETE, RA_ASYNC_ROLLBACK_VNF, RA_ASYNC_ROLLBACK_VNF_COMPLETE, RA_ROLLBACK_VNF_ERR, RA_DB_INVALID_STATUS, RA_CANT_UPDATE_REQUEST, RA_DB_REQUEST_NOT_EXIST, RA_CONFIG_NOT_FOUND, RA_CONFIG_LOAD, RA_RECEIVE_WORKFLOW_MESSAGE,\r
+  // BPEL engine Messages\r
+  BPMN_GENERAL_INFO, BPMN_GENERAL_EXCEPTION_ARG, BPMN_GENERAL_EXCEPTION, BPMN_GENERAL_WARNING, BPMN_AUDIT_EXEC, BPMN_GENERAL_METRICS, BPMN_URN_MAPPING_FAIL, BPMN_VARIABLE_NULL, BPMN_CALLBACK_EXCEPTION,\r
+  // ASDC Messages\r
+  ASDC_GENERAL_EXCEPTION_ARG, ASDC_GENERAL_EXCEPTION, ASDC_GENERAL_WARNING, ASDC_GENERAL_INFO, ASDC_AUDIT_EXEC, ASDC_GENERAL_METRICS, ASDC_CREATE_SERVICE, ASDC_ARTIFACT_ALREADY_DEPLOYED, ASDC_CREATE_ARTIFACT, ASDC_ARTIFACT_INSTALL_EXC, ASDC_ARTIFACT_ALREADY_DEPLOYED_DETAIL, ASDC_ARTIFACT_NOT_DEPLOYED_DETAIL, ASDC_ARTIFACT_CHECK_EXC, ASDC_INIT_ASDC_CLIENT_EXC, ASDC_INIT_ASDC_CLIENT_SUC, ASDC_LOAD_ASDC_CLIENT_EXC, ASDC_SINGLETON_CHECKT_EXC, ASDC_SHUTDOWN_ASDC_CLIENT_EXC, ASDC_CHECK_HEAT_TEMPLATE, ASDC_START_INSTALL_ARTIFACT, ASDC_ARTIFACT_TYPE_NOT_SUPPORT, ASDC_ARTIFACT_ALREADY_EXIST, ASDC_ARTIFACT_DOWNLOAD_SUC, ASDC_ARTIFACT_DOWNLOAD_FAIL, ASDC_START_DEPLOY_ARTIFACT, ASDC_SEND_NOTIF_ASDC, ASDC_SEND_NOTIF_ASDC_EXEC, ASDC_RECEIVE_CALLBACK_NOTIF, ASDC_RECEIVE_SERVICE_NOTIF, ASDC_ARTIFACT_NULL, ASDC_SERVICE_NOT_SUPPORT, ASDC_ARTIFACT_DEPLOY_SUC, ASDC_PROPERTIES_NOT_FOUND, ASDC_PROPERTIES_LOAD_SUCCESS,\r
+  // Default Messages, in case Log catalog is not defined\r
+  GENERAL_EXCEPTION_ARG, GENERAL_EXCEPTION, GENERAL_WARNING, AUDIT_EXEC, GENERAL_METRICS, LOGGER_SETUP, LOGGER_NOT_FOUND, LOGGER_UPDATE_SUC, LOGGER_UPDATE_DEBUG, LOGGER_UPDATE_DEBUG_SUC, LOAD_PROPERTIES_SUC, NO_PROPERTIES, MADATORY_PARAM_MISSING, LOAD_PROPERTIES_FAIL, INIT_LOGGER, INIT_LOGGER_FAIL, JAXB_EXCEPTION, IDENTITY_SERVICE_NOT_FOUND\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java
new file mode 100644 (file)
index 0000000..0bbab4a
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.permissions;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+\r
+import java.util.Collection;\r
+\r
+public class PermissionChecker {\r
+    //check is a user have a certain permission in a group\r
+    public static boolean hasPermissionTo(User user, Group group, String permission, GroupRepository groupRepository){\r
+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);\r
+        return hasPermissionTo(userPermission,group,permission);\r
+    }\r
+    public static boolean hasPermissionTo(User user, Group group, Collection<String> permissions, GroupRepository groupRepository){\r
+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);\r
+        for(String permission : permissions){\r
+            if(!hasPermissionTo(userPermission,group,permission)){\r
+                return false;\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+    // check a users list of permission in a group\r
+    private static boolean hasPermissionTo(UserPermission userPermission, Group group, String permission){\r
+        switch (permission.toUpperCase()) {\r
+            case (UserPermission.Permission.READ):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.READ);\r
+            case (UserPermission.Permission.WRITE):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.WRITE);\r
+            case (UserPermission.Permission.EXECUTE):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.EXECUTE);\r
+            case (UserPermission.Permission.DELETE):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.DELETE);\r
+            case (UserPermission.Permission.MANAGEMENT):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.MANAGEMENT);\r
+            default:\r
+                return false;// reaches here when permission provided is not an option\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java
new file mode 100644 (file)
index 0000000..2a180bc
--- /dev/null
@@ -0,0 +1,238 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.permissions;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.GroupMember;\r
+import org.oran.otf.common.model.Role;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+\r
+import java.util.*;\r
+\r
+public class PermissionUtil {\r
+    //build userPermission object which contains all access control information of the user\r
+    public UserPermission buildUserPermission(User user, GroupRepository groupRepository) {\r
+        UserPermission userPermission = new UserPermission();\r
+        userPermission.setUser(user);\r
+        Map<String,Set<String>> userAccessMap; // map from group to permission that user have in that group\r
+\r
+        userAccessMap = mapGroupsToPermission(user,groupRepository);\r
+        userPermission.setUserAccessMap(userAccessMap);\r
+        return userPermission;\r
+    }\r
+    // return if user have specified permission in a certain group\r
+    // ***********only use this on groups that the user is in directly (non-child and non parents)****************\r
+    public static boolean hasDirectPermissionTo(String permission, User user, Group group) {\r
+        Set<String> possiblePermissions= getUserGroupPermissions(user,group);\r
+        return possiblePermissions.stream().anyMatch(p-> p.equalsIgnoreCase(permission)); //\r
+    }\r
+    // Get all the permissions the user have in a certain group\r
+    public static Set<String> getUserGroupPermissions(User user, Group group){\r
+        Set<String> permissionsAllowed = new HashSet<>();\r
+        Set<String> usersAssignedRoles = findUserRoles(user,group);\r
+        if(usersAssignedRoles.isEmpty()) // empty set permissions because the user have no roles in the group aka not a member\r
+            return permissionsAllowed;\r
+        //get every single permissions for each role that the user have.\r
+        for(String role : usersAssignedRoles){\r
+             permissionsAllowed.addAll(getRolePermissions(role,group));\r
+        }\r
+        return permissionsAllowed;\r
+    }\r
+    //get the permissions associated with the userRoleName in group\r
+    public static Set<String> getRolePermissions(String userRoleName, Group group)\r
+    {\r
+        for(Role role : group.getRoles())\r
+        {\r
+            if(role.getRoleName().equalsIgnoreCase(userRoleName))\r
+            {\r
+                return new HashSet<String>(role.getPermissions());\r
+            }\r
+        }\r
+        return new HashSet<String>(); // empty string set if the role name cant be found in the group\r
+    }\r
+    // find the user's role in the specified group\r
+    public static Set<String> findUserRoles(User user, Group group){\r
+        for(GroupMember member : group.getMembers())\r
+        {\r
+            // if userId matches then get all the user's role in the group\r
+            if(member.getUserId().toString().equals(user.get_id().toString()))\r
+                return new HashSet<String>(member.getRoles());\r
+        }\r
+        return new HashSet<String>(); //if user have no roles\r
+    }\r
+\r
+    // create map that where key is the group id and value = users permission (string) that that group\r
+    private Map<String,Set<String>> mapGroupsToPermission(User user, GroupRepository groupRepository){\r
+        Map<String,Set<String>> groupAccessMap = new HashMap<>();\r
+        List<Group> enrolledGroups = groupRepository.findAllByMembersId(user.get_id());// enrolledGroups = groups that user is a member of\r
+        Map<String, Group> allGroupMap = groupListToMap(groupRepository.findAll());\r
+        // get all permission in the groups the user is ia member of\r
+        for(Group group: enrolledGroups) {\r
+            Set<String> permissions = getUserGroupPermissions(user,group);\r
+            groupAccessMap.put(group.get_id().toString(),convertPermissions(permissions));\r
+        }\r
+        //assign add read to all parent groups\r
+        Set<String> parentGroupsId = getParentGroups(enrolledGroups,allGroupMap);\r
+        for(String parentId : parentGroupsId)\r
+        {\r
+            // if parent access role already exist in\r
+            // group access map cause they are a member\r
+            if(groupAccessMap.get(parentId)!= null)\r
+                groupAccessMap.get(parentId).add(UserPermission.Permission.READ);\r
+            else\r
+                groupAccessMap.put(parentId,new HashSet<String>(Arrays.asList(UserPermission.Permission.READ)));\r
+        }\r
+        // if there is management role\r
+        // then assign read access to children\r
+        if(hasManagementRole(user,enrolledGroups)){\r
+//            Set<String>childIds = getChildrenGroupsId(enrolledGroups,allGroupMap,user);\r
+            for(Group enrolledGroup : enrolledGroups) {\r
+                // if enrolled groups is a management group\r
+                if(hasDirectPermissionTo(UserPermission.Permission.MANAGEMENT,user,enrolledGroup)){\r
+                    // if there is management role then get all the child of that group, do this for all management groups\r
+                    Set<String> childIds= getChildrenGroupsId(Arrays.asList(enrolledGroup),allGroupMap,user);\r
+                    Set<String> userGroupPermissions = convertPermissions(getUserGroupPermissions(user,enrolledGroup));\r
+                    for(String childId : childIds){\r
+                        if (groupAccessMap.get(childId) != null)\r
+                            groupAccessMap.get(childId).addAll(userGroupPermissions);\r
+                        else{\r
+                            groupAccessMap.put(childId,userGroupPermissions);\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return groupAccessMap;\r
+    }\r
+    // check is user have managementRole\r
+    private boolean hasManagementRole(User user, List<Group> enrolledGroups)\r
+    {\r
+        for(Group group: enrolledGroups){\r
+            if(hasDirectPermissionTo(UserPermission.Permission.MANAGEMENT,user,group))\r
+            {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+    // get the parent groups starting from the enrolled group of the user\r
+    private Set<String> getParentGroups(List<Group> enrolledGroup, Map<String, Group> groupMap )\r
+    {\r
+        Set<String> parentGroups = new HashSet<>();\r
+        return lookUp(enrolledGroup,groupMap,parentGroups);\r
+    }\r
+    //recursive lookup starting at the enrolled groups that the user is a member of\r
+    private Set<String> lookUp(List<Group> groupsToCheck, Map<String, Group> groupMap, Set<String> resultSet)\r
+    {\r
+        //base case: nothing to check anymore\r
+        if(groupsToCheck.isEmpty())\r
+            return resultSet;\r
+        //This is the parents directly above the current groups that are being checked\r
+        List<Group> currentParentGroups = new ArrayList<>();\r
+\r
+        for(Group group : groupsToCheck)\r
+        {\r
+            if(group.getParentGroupId() != null) // if there is a parent\r
+            {\r
+                String parentId = group.getParentGroupId().toString();\r
+                Group parentGroup = groupMap.get(parentId);\r
+                resultSet.add(parentId);\r
+                currentParentGroups.add(parentGroup); // add to currentParentGroup so it can be used recursively check for more parents\r
+            }\r
+        }\r
+        return lookUp(currentParentGroups,groupMap,resultSet);\r
+    }\r
+    // convert a list of groups to a map of group ids to group\r
+    private Map<String, Group> groupListToMap(List<Group> allGroups)\r
+    {\r
+        Map<String, Group> groupMap = new HashMap<>();\r
+        allGroups.forEach(group -> groupMap.put(group.get_id().toString(),group));\r
+        return groupMap;\r
+    }\r
+    //get all the child group\r
+    private Set<String> getChildrenGroupsId(List<Group> enrolledGroup, Map<String, Group> allGroupsMap, User user)\r
+    {\r
+        Set<String> childrenGroups = new HashSet<>();\r
+        Set<String> managementGroupIds = getManagementGroupIds(enrolledGroup,user);\r
+        return  lookForChildren(managementGroupIds,allGroupsMap,childrenGroups);\r
+    }\r
+\r
+    private Set<String> getManagementGroupIds(List<Group> enrolledGroups, User user)\r
+    {\r
+        Set<String> parentIds = new HashSet<>();\r
+        for(Group group: enrolledGroups)\r
+        {\r
+            if(hasDirectPermissionTo(UserPermission.Permission.MANAGEMENT,user,group)) // has Management permission\r
+            {\r
+                parentIds.add(group.get_id().toString());\r
+            }\r
+        }\r
+        return parentIds;\r
+    }\r
+    //recursive look down for childrens via breath first search\r
+    private Set<String> lookForChildren (Set<String> parentIds, Map<String, Group> allGroupsMap, Set<String> resultSet)\r
+    {\r
+        //base case = no groups to check anymore;\r
+        if (parentIds.isEmpty())\r
+            return resultSet;\r
+\r
+        Set<String> currentChildrenIds = new HashSet<>();\r
+        for(String groupId : allGroupsMap.keySet())\r
+        {\r
+            Group possibleChildGroup = allGroupsMap.get(groupId);\r
+            if(isChildOf(parentIds,possibleChildGroup)) // if parent id is the same\r
+            {\r
+                currentChildrenIds.add(groupId);\r
+                resultSet.add(groupId);\r
+            }\r
+        }\r
+        return lookForChildren(currentChildrenIds,allGroupsMap,resultSet);\r
+    }\r
+    //check if a group is a child of a list of parent group ids\r
+    private boolean isChildOf(Set<String>parentGroupIds, Group childGroup){\r
+        for(String parentId: parentGroupIds)\r
+        {\r
+            if(isChildOf(parentId,childGroup))\r
+                return true;\r
+        }\r
+        return false;\r
+    }\r
+    //check is group has parent that is specified by parentId\r
+    private boolean isChildOf(String parentId, Group childGroup) {\r
+        if(childGroup.getParentGroupId() == null)\r
+            return false;\r
+       return childGroup.getParentGroupId().toString().equals(parentId);\r
+    }\r
+\r
+    private Set<String> convertPermissions (Set<String> permissions){\r
+        Set<String> result = new HashSet<>();\r
+        for (String permission: permissions){\r
+            if(permission.equalsIgnoreCase(UserPermission.Permission.READ))\r
+                result.add(UserPermission.Permission.READ);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.WRITE))\r
+                result.add(UserPermission.Permission.WRITE);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.DELETE))\r
+                result.add(UserPermission.Permission.DELETE);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.EXECUTE))\r
+                result.add(UserPermission.Permission.EXECUTE);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.MANAGEMENT))\r
+                result.add(UserPermission.Permission.MANAGEMENT);\r
+        }\r
+            return result;\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java
new file mode 100644 (file)
index 0000000..1883721
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.permissions;\r
+\r
+import org.oran.otf.common.model.User;\r
+\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+public class UserPermission {\r
+    private User user;\r
+    private Map<String,Set<String>> userAccessMap;\r
+\r
+    public User getUser() {\r
+        return user;\r
+    }\r
+\r
+    public void setUser(User user) {\r
+        this.user = user;\r
+    }\r
+\r
+    public Map<String, Set<String>> getUserAccessMap() {\r
+        return userAccessMap;\r
+    }\r
+\r
+    public void setUserAccessMap(Map<String,Set<String>> userAccessMap) {\r
+        this.userAccessMap = userAccessMap;\r
+    }\r
+\r
+    public boolean  hasAccessTo(String groupId,String permission) {\r
+        if (userAccessMap.get(groupId) == null) {\r
+            return false;\r
+        }\r
+        Set<String> group = userAccessMap.get(groupId);\r
+        return group.stream().anyMatch(groupPermission->groupPermission.equalsIgnoreCase(permission));\r
+    }\r
+    public class Permission{\r
+        public static final String READ = "READ";\r
+        public static final String WRITE = "WRITE";\r
+        public static final String EXECUTE = "EXECUTE";\r
+        public static final String DELETE = "DELETE";\r
+        public static final String MANAGEMENT ="MANAGEMENT";\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/common/utility/sftp/SftpUtility.java b/otf-camunda/src/main/java/org/oran/otf/common/utility/sftp/SftpUtility.java
new file mode 100644 (file)
index 0000000..cdaf8fd
--- /dev/null
@@ -0,0 +1,94 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.sftp;\r
+\r
+import org.apache.commons.io.IOUtils;\r
+import org.apache.commons.vfs2.*;\r
+import org.apache.commons.vfs2.provider.sftp.IdentityInfo;\r
+import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;\r
+\r
+import java.io.File;\r
+import java.io.InputStream;\r
+\r
+\r
+public class SftpUtility {\r
+\r
+    public static byte[] getFile(String host, String artifactPath, String privateKeyPath, String privateKeyUsername, String privateKeyPasspharase) throws Exception {\r
+        String remoteURI = "sftp://" + privateKeyUsername + "@" + host + "/" + artifactPath;\r
+\r
+        FileSystemOptions fsOptions = new FileSystemOptions();\r
+        FileSystemManager fsManager = null;\r
+        byte[] bytes = null;\r
+        SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();\r
+        builder.setUserDirIsRoot(fsOptions, false);\r
+        builder.setStrictHostKeyChecking(fsOptions, "no");\r
+        IdentityInfo identityInfo = new IdentityInfo(new File(privateKeyPath), privateKeyPasspharase.getBytes());\r
+        builder.setIdentityInfo(fsOptions, identityInfo);\r
+        fsManager = VFS.getManager();\r
+        FileObject remoteFileObject = fsManager.resolveFile(remoteURI, fsOptions);\r
+        if(!remoteFileObject.isFile()) {\r
+            remoteFileObject.close();\r
+            throw new Exception("Expected a file, but supplied filePath was not a file.");\r
+        }\r
+        InputStream is = remoteFileObject.getContent().getInputStream();\r
+        bytes = IOUtils.toByteArray(is);\r
+        remoteFileObject.close();\r
+        return bytes;\r
+\r
+    }\r
+\r
+    public static FileObject getDirectory(String host, String artifactPath, String privateKeyPath, String privateKeyUsername, String privateKeyPasspharase) throws Exception {\r
+        String remoteURI = "sftp://" + privateKeyUsername + "@" + host + "/" + artifactPath;\r
+\r
+        FileSystemOptions fsOptions = new FileSystemOptions();\r
+        FileSystemManager fsManager = null;\r
+        SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();\r
+        builder.setUserDirIsRoot(fsOptions, false);\r
+        builder.setStrictHostKeyChecking(fsOptions, "no");\r
+        IdentityInfo identityInfo = new IdentityInfo(new File(privateKeyPath), privateKeyPasspharase.getBytes());\r
+        builder.setIdentityInfo(fsOptions, identityInfo);\r
+        fsManager = VFS.getManager();\r
+        FileObject fileObject = fsManager.resolveFile(remoteURI, fsOptions);\r
+        if(fileObject.isFolder()) {\r
+            return fileObject;\r
+        }\r
+        fileObject.close();\r
+        throw new Exception("Expected a folder, but supplied filePath was not a folder.");\r
+    }\r
+\r
+    public static void uploadFile(String host, String artifactPath, String privateKeyPath, String privateKeyUsername, String privateKeyPasspharase, File tempFile) throws Exception {\r
+        String remoteURI = "sftp://" + privateKeyUsername + "@" + host + "/" + artifactPath;\r
+\r
+        FileSystemOptions fsOptions = new FileSystemOptions();\r
+        FileSystemManager fsManager = null;\r
+        SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();\r
+        builder.setUserDirIsRoot(fsOptions, false);\r
+        builder.setStrictHostKeyChecking(fsOptions, "no");\r
+        IdentityInfo identityInfo = new IdentityInfo(new File(privateKeyPath), privateKeyPasspharase.getBytes());\r
+        builder.setIdentityInfo(fsOptions, identityInfo);\r
+        fsManager = VFS.getManager();\r
+        //resolve file location\r
+        FileObject remoteFileObject = fsManager.resolveFile(remoteURI, fsOptions);\r
+        FileObject sourceFile = fsManager.resolveFile(tempFile.getAbsolutePath());\r
+        //if file exists then override, else create file\r
+        remoteFileObject.copyFrom(sourceFile, Selectors.SELECT_SELF);\r
+        remoteFileObject.close();\r
+        sourceFile.close();\r
+    }\r
+\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/event/TestInstanceCompletionEvent.java b/otf-camunda/src/main/java/org/oran/otf/event/TestInstanceCompletionEvent.java
new file mode 100644 (file)
index 0000000..0d21acf
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.event;\r
+\r
+import com.google.gson.JsonObject;\r
+import org.camunda.bpm.engine.delegate.DelegateExecution;\r
+import org.springframework.context.ApplicationEvent;\r
+\r
+public class TestInstanceCompletionEvent extends ApplicationEvent {\r
+\r
+  private JsonObject obj;\r
+  private DelegateExecution execution;\r
+\r
+  public TestInstanceCompletionEvent(Object source, JsonObject obj, DelegateExecution execution) {\r
+    super(source);\r
+    this.obj = obj;\r
+    this.execution = execution;\r
+  }\r
+\r
+  public JsonObject getObj() {\r
+    return obj;\r
+  }\r
+\r
+  public void setObj(JsonObject obj) {\r
+    this.obj = obj;\r
+  }\r
+\r
+  public DelegateExecution getExecution() {\r
+    return execution;\r
+  }\r
+\r
+  public void setExecution(DelegateExecution execution) {\r
+    this.execution = execution;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/event/TestInstanceIncidentEvent.java b/otf-camunda/src/main/java/org/oran/otf/event/TestInstanceIncidentEvent.java
new file mode 100644 (file)
index 0000000..0bb5eb6
--- /dev/null
@@ -0,0 +1,59 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.event;\r
+\r
+import org.camunda.bpm.engine.impl.incident.IncidentContext;\r
+import org.springframework.context.ApplicationEvent;\r
+\r
+public class TestInstanceIncidentEvent extends ApplicationEvent {\r
+\r
+  private IncidentContext context;\r
+  private String message;\r
+  private String type;\r
+\r
+  public TestInstanceIncidentEvent(Object source, IncidentContext context, String message,\r
+      String type) {\r
+    super(source);\r
+    this.context = context;\r
+    this.message = message;\r
+    this.type = type;\r
+  }\r
+\r
+  public IncidentContext getContext() {\r
+    return context;\r
+  }\r
+\r
+  public void setContext(IncidentContext context) {\r
+    this.context = context;\r
+  }\r
+\r
+  public String getMessage() {\r
+    return message;\r
+  }\r
+\r
+  public void setMessage(String message) {\r
+    this.message = message;\r
+  }\r
+\r
+  public String getType() {\r
+    return type;\r
+  }\r
+\r
+  public void setType(String type) {\r
+    this.type = type;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/DeleteProcessInstanceService.java b/otf-camunda/src/main/java/org/oran/otf/service/DeleteProcessInstanceService.java
new file mode 100644 (file)
index 0000000..d627f31
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+\r
+import javax.ws.rs.DELETE;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.Response;\r
+\r
+@Path("/tcu")\r
+public interface DeleteProcessInstanceService {\r
+\r
+    @DELETE\r
+    @Path("/delete-process-instance/v1/{executionId}")\r
+    @Produces(APPLICATION_JSON)\r
+    Response deleteProcessInstance(@PathParam("executionId") String executionId);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/DeleteTestDefinitionService.java b/otf-camunda/src/main/java/org/oran/otf/service/DeleteTestDefinitionService.java
new file mode 100644 (file)
index 0000000..b78d23d
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+import javax.ws.rs.*;\r
+import javax.ws.rs.core.Response;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+\r
+@Path("/tcu")\r
+public interface DeleteTestDefinitionService {\r
+\r
+    @DELETE\r
+    @Path("/delete-test-strategy/v1/deployment-id/{deploymentId}/")\r
+    @Consumes(APPLICATION_JSON)\r
+    @Produces(APPLICATION_JSON)\r
+    Response deleteTestStrategyByDeploymentId(@PathParam("deploymentId") String deploymentId);\r
+\r
+\r
+\r
+    @DELETE\r
+    @Path("/delete-test-strategy/v1/test-definition-id/{testDefinitionId}/")\r
+    @Consumes(APPLICATION_JSON)\r
+    @Produces(APPLICATION_JSON)\r
+    public Response deleteTestStrategyByTestDefinitionId(@PathParam("testDefinitionId") String testDefinitionId);\r
+\r
+\r
+\r
+//    @DELETE\r
+//    @Path("/delete-all-test-strategies/v1/")\r
+//    @Consumes(APPLICATION_JSON)\r
+//    @Produces(APPLICATION_JSON)\r
+//    Response deleteAllTestStrategies();\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/DeveloperService.java b/otf-camunda/src/main/java/org/oran/otf/service/DeveloperService.java
new file mode 100644 (file)
index 0000000..3e6d036
--- /dev/null
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.ws.rs.Consumes;\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.POST;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.Context;\r
+import javax.ws.rs.core.Response;\r
+\r
+@Path("/tcu")\r
+public interface DeveloperService {\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/workflowTaskCleanup/v1/{enabled}")\r
+  Response workflowTaskCleanup(@PathParam("enabled") String enabled);\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/externalTaskWorker/v1/{enabled}")\r
+  Response externalTaskWorker(@PathParam("enabled") String enabled);\r
+\r
+  @GET\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/printThreads/v1")\r
+  Response printThreads(@Context HttpServletRequest request);\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/jobExecutor/v1/activate")\r
+  Response activateJobExecutor();\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/jobExecutor/v1/deactivate")\r
+  Response deActivateJobExecutor();\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/gracefulshutdown/v1")\r
+  Response gracefulShutdown();\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/disableGracefulShutdown/v1")\r
+  Response disableGracefulShutdown();\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/dev/enableGracefulShutdown/v1")\r
+  Response enableGracefulShutdown();\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/HealthService.java b/otf-camunda/src/main/java/org/oran/otf/service/HealthService.java
new file mode 100644 (file)
index 0000000..065d886
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+\r
+import javax.ws.rs.Consumes;\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.Response;\r
+\r
+@Path("/") // Base path for unauthenticated services\r
+public interface HealthService {\r
+\r
+  @GET\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/health/v1")\r
+  Response getHealth();\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/ProcessInstanceCompletionService.java b/otf-camunda/src/main/java/org/oran/otf/service/ProcessInstanceCompletionService.java
new file mode 100644 (file)
index 0000000..9d5e575
--- /dev/null
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+\r
+\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.Response;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+\r
+@Path("/tcu")\r
+public interface ProcessInstanceCompletionService {\r
+\r
+    @GET\r
+    @Produces(APPLICATION_JSON)\r
+    @Path("/process-instance-completion-check/v1/{id}")\r
+    public Response isProcessInstanceComplete(@PathParam("id") String processInstanceId);\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/TestControlUnitService.java b/otf-camunda/src/main/java/org/oran/otf/service/TestControlUnitService.java
new file mode 100644 (file)
index 0000000..e6ed4fc
--- /dev/null
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+\r
+import javax.ws.rs.Consumes;\r
+import javax.ws.rs.POST;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.Context;\r
+import javax.ws.rs.core.HttpHeaders;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.MultivaluedMap;\r
+import javax.ws.rs.core.Response;\r
+import org.springframework.web.bind.annotation.RequestHeader;\r
+\r
+@Path("/tcu")\r
+public interface TestControlUnitService {\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/execute/testInstanceId/{testInstanceId}")\r
+  Response executeByTestInstanceId(@PathParam("testInstanceId") String testInstanceId);\r
+\r
+  @POST\r
+  @Consumes(APPLICATION_JSON)\r
+  @Produces(APPLICATION_JSON)\r
+  @Path("/execute/workflowRequest")\r
+  Response executeByWorkflowRequest(String workflowRequest);\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/TestDefinitionDeploymentService.java b/otf-camunda/src/main/java/org/oran/otf/service/TestDefinitionDeploymentService.java
new file mode 100644 (file)
index 0000000..e329652
--- /dev/null
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service;\r
+\r
+\r
+import org.glassfish.jersey.media.multipart.FormDataParam;\r
+\r
+import javax.ws.rs.*;\r
+import javax.ws.rs.core.Response;\r
+\r
+import java.io.InputStream;\r
+\r
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;\r
+import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA;\r
+\r
+@Path("/tcu")\r
+public interface TestDefinitionDeploymentService {\r
+\r
+    @POST\r
+    @Path("/deploy-test-strategy-zip/v1")\r
+    @Consumes(MULTIPART_FORM_DATA)\r
+    @Produces(APPLICATION_JSON)\r
+    Response deployTestStrategyWithResources(@FormDataParam("bpmn") InputStream bpmn,\r
+                                             @FormDataParam("resources") InputStream resourcesZip);\r
+\r
+    @GET\r
+    @Path("/testDefinition/v1/processDefinitionKey/{processDefinitionKey}")\r
+    @Consumes(APPLICATION_JSON)\r
+    @Produces(APPLICATION_JSON)\r
+    Response isProcessDefinitionDeployed(@PathParam("processDefinitionKey") String processDefinitionKey);\r
+\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteProcessInstanceServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteProcessInstanceServiceImpl.java
new file mode 100644 (file)
index 0000000..8b51c90
--- /dev/null
@@ -0,0 +1,96 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.service.ProcessEngineAwareService;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.DeleteProcessInstanceService;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.runtime.ProcessInstance;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class DeleteProcessInstanceServiceImpl extends ProcessEngineAwareService\r
+    implements DeleteProcessInstanceService {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(DeleteProcessInstanceServiceImpl.class);\r
+\r
+  @Override\r
+  public Response deleteProcessInstance(String executionId) {\r
+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+\r
+    Map<String, Object> response =\r
+        deleteProcessInstanceInternal(\r
+            executionId, "Deleted via TCU endpoint at " + new Date(System.currentTimeMillis()));\r
+\r
+    try {\r
+      int code = (int) response.get("code");\r
+      String sRes = Convert.mapToJson(response);\r
+      if (code == 404) {\r
+        return ResponseUtility.Build.notFoundWithMessage(sRes);\r
+      } else if (code == 200) {\r
+        return ResponseUtility.Build.okRequestWithMessage(sRes);\r
+      }\r
+    } catch (ClassCastException cce) {\r
+      logger.error(cce.getMessage());\r
+    }\r
+    // Unhandled response\r
+    return ResponseUtility.Build.internalServerError();\r
+  }\r
+\r
+  public Map<String, Object> deleteProcessInstanceInternal(\r
+      String executionId, String deleteReason) {\r
+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+\r
+    ProcessInstance pi =\r
+        runtimeService.createProcessInstanceQuery().processInstanceId(executionId).singleResult();\r
+\r
+    Map<String, Object> response = new HashMap<>();\r
+\r
+    if (pi == null) {\r
+      response.put(\r
+          "result",\r
+          String.format("A process instance with the executionId %s was not found.", executionId));\r
+      response.put("code", 404);\r
+    } else {\r
+      List<WorkflowTask> workflowTasks = WorkflowTask.workflowTasksByExecutionId.get(executionId);\r
+      if (workflowTasks != null) {\r
+        for (WorkflowTask workflowTask : workflowTasks) {\r
+          workflowTask.shutdown();\r
+        }\r
+      }\r
+\r
+      runtimeService.deleteProcessInstance(executionId, deleteReason);\r
+      response.put("result", "Successfully deleted.");\r
+      response.put("code", 200);\r
+    }\r
+\r
+    return response;\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteTestDefinitionServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteTestDefinitionServiceImpl.java
new file mode 100644 (file)
index 0000000..79eeb45
--- /dev/null
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.repository.TestDefinitionRepository;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.DeleteTestDefinitionService;\r
+import java.util.List;\r
+import java.util.Optional;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RepositoryService;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class DeleteTestDefinitionServiceImpl implements DeleteTestDefinitionService {\r
+\r
+\r
+    private RepositoryService repositoryService;\r
+\r
+    @Autowired\r
+    private TestDefinitionRepository testDefinitionRepository;\r
+\r
+    @EventListener(ApplicationReadyEvent.class)\r
+    private void initialize(){\r
+        if(this.repositoryService == null){\r
+            this.repositoryService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRepositoryService();\r
+        }\r
+    }\r
+\r
+    // delete a single version by deploymentId\r
+    @Override\r
+    public Response deleteTestStrategyByDeploymentId(String deploymentId) {\r
+        try {\r
+            repositoryService.deleteDeployment(deploymentId, true);\r
+            return ResponseUtility.Build.okRequest();\r
+        } catch (Exception e) {\r
+            return ResponseUtility.Build.badRequestWithMessage(e.getMessage());\r
+        }\r
+    }\r
+\r
+\r
+    // delete all deployment versions given test definition\r
+    @Override\r
+    public Response deleteTestStrategyByTestDefinitionId(String testDefinitionId) {\r
+        Optional<TestDefinition> testDefinitionQuery =\r
+                testDefinitionRepository.findById(testDefinitionId);\r
+        if (!testDefinitionQuery.isPresent()) {\r
+            return Response.status(404).build();\r
+        }\r
+        TestDefinition testDefinition = testDefinitionQuery.get();\r
+\r
+        List<BpmnInstance> bpmnInstances = testDefinition.getBpmnInstances();\r
+        // List<Integer> indices = new ArrayList<Integer>();\r
+        for (int i = 0; i < bpmnInstances.size(); i++) {\r
+            try {\r
+                repositoryService.deleteDeployment(bpmnInstances.get(i).getDeploymentId(), true);\r
+                // indices.add(i);\r
+            } catch (Exception e) {\r
+\r
+            }\r
+        }\r
+\r
+        // for(int i = indices.size()-1; i >=0; i++) {\r
+        // bpmnInstances.remove(i);\r
+        // }\r
+        // testDefinition.setBpmnInstances(new ArrayList<BpmnInstance>());\r
+        // testDefinitionRepository.save(testDefinition);\r
+        return ResponseUtility.Build.okRequest();\r
+\r
+\r
+    }\r
+\r
+\r
+    // delete all deployments\r
+//    public Response deleteAllTestStrategies() {\r
+////         create a database to retrieve all process definitions\r
+//        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()\r
+//                .orderByProcessDefinitionVersion().asc().list();\r
+//\r
+//        final int sizeBefore = processDefinitions.size();\r
+//\r
+//        // delete each deployment\r
+//        processDefinitions.forEach(processDefinition -> {\r
+//            repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true);\r
+//        });\r
+//\r
+//        final int sizeAfter = processDefinitions.size();\r
+//\r
+//        Map<String, Object> response = new HashMap<String, Object>();\r
+//        if (sizeBefore - sizeAfter == 0)\r
+//            response.put("numDeletions", sizeBefore);\r
+//        else\r
+//            response.put("numDeletions", sizeBefore - sizeAfter);\r
+//\r
+//        return ResponseUtility.Build.okRequestWithObject(response);\r
+//    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/DeveloperServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/DeveloperServiceImpl.java
new file mode 100644 (file)
index 0000000..7501be7
--- /dev/null
@@ -0,0 +1,161 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.camunda.service.CamundaShutdown;\r
+import org.oran.otf.camunda.service.OtfExternalTaskService;\r
+import org.oran.otf.camunda.service.OtfWorkflowTaskCleanupService;\r
+import org.oran.otf.camunda.workflow.utility.WorkflowTask;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.DeveloperService;\r
+import com.google.common.base.Strings;\r
+\r
+import java.util.Arrays;\r
+import java.util.Set;\r
+\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.context.event.ContextClosedEvent;\r
+import org.springframework.context.event.EventListener;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;\r
+import org.camunda.bpm.engine.impl.jobexecutor.JobExecutor;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class DeveloperServiceImpl implements DeveloperService {\r
+       \r
+  private Logger logger = LoggerFactory.getLogger(DeveloperServiceImpl.class);\r
+  \r
+  @Autowired\r
+  CamundaShutdown camundaShutdown;\r
+  \r
+  @Value("${otf.camunda.graceful-shutdown.wait-time}")\r
+  private int gracefulWaitTime;\r
+\r
+  private boolean gracefulShutdown = true;\r
+\r
+  @Override\r
+  public Response workflowTaskCleanup(String enabled) {\r
+    if (Strings.isNullOrEmpty(enabled))\r
+      return ResponseUtility.Build.badRequestWithMessage(\r
+          "Path parameter, enabled, cannot be null or empty.");\r
+\r
+    OtfWorkflowTaskCleanupService.isEnabled = enabled.equalsIgnoreCase("true");\r
+    return ResponseUtility.Build.okRequestWithMessage(\r
+        "Clean up service set to " + OtfWorkflowTaskCleanupService.isEnabled);\r
+  }\r
+\r
+  @Override\r
+  public Response externalTaskWorker(String enabled) {\r
+    if (Strings.isNullOrEmpty(enabled)) {\r
+      return ResponseUtility.Build.badRequestWithMessage(\r
+          "Path parameter, enabled, cannot be null or empty.");\r
+    }\r
+\r
+    OtfExternalTaskService.isEnabled = enabled.equalsIgnoreCase("true");\r
+    return ResponseUtility.Build.okRequestWithMessage(\r
+        "OTF External Task set to " + OtfExternalTaskService.isEnabled);\r
+  }\r
+\r
+  @Override\r
+  public Response printThreads(HttpServletRequest request) {\r
+    //Logger logger = LoggerFactory.getLogger(HealthServiceImpl.class);\r
+    String message = String.format("Health request from %s.", request.getRemoteAddr());\r
+    logger.info(message);\r
+\r
+    WorkflowTask.printWorkflowTaskResources();\r
+    logger.info("");\r
+    logger.info("");\r
+    WorkflowTask.printThreadInformation();\r
+\r
+    return ResponseUtility.Build.okRequestWithMessage(message);\r
+  }\r
+  \r
+  @Override\r
+  public Response activateJobExecutor() {\r
+       JobExecutor jobExecutor = ((ProcessEngineConfigurationImpl) (BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName)).getProcessEngineConfiguration()).getJobExecutor();\r
+    if (!jobExecutor.isActive()) {\r
+               jobExecutor.start();\r
+    }\r
+       return ResponseUtility.Build.okRequest();\r
+  }\r
+\r
+  @Override\r
+  public Response deActivateJobExecutor() {\r
+       JobExecutor jobExecutor = ((ProcessEngineConfigurationImpl) (BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName)).getProcessEngineConfiguration()).getJobExecutor();\r
+       if (jobExecutor.isActive()) {\r
+               jobExecutor.shutdown();\r
+       }\r
+       return ResponseUtility.Build.okRequest();\r
+  }\r
+  \r
+  @Override\r
+  public Response gracefulShutdown() {\r
+         return this.gracefulShutdown ? ResponseUtility.Build.okRequestWithMessage(shutdown()) : ResponseUtility.Build.okRequestWithMessage("Graceful shutdown is disabled.");\r
+  }\r
+\r
+    @Override\r
+    public Response disableGracefulShutdown() {\r
+        this.gracefulShutdown = false;\r
+        return ResponseUtility.Build.okRequest();\r
+    }\r
+\r
+    @Override\r
+    public Response enableGracefulShutdown() {\r
+        this.gracefulShutdown = true;\r
+        return ResponseUtility.Build.okRequest();\r
+    }\r
+\r
+    @EventListener(ContextClosedEvent.class)\r
+  private String shutdown() {\r
+         String message = "Graceful shutdown:";\r
+         String returnMessage = "Graceful shutdown processes terminated: ";\r
+         try {\r
+             //disable external task service\r
+          OtfExternalTaskService.isEnabled = false;\r
+          //disable job executor\r
+                 deActivateJobExecutor();\r
+          logger.info("Disabled job executor and external task service.");\r
+          logger.info("Starting to sleep...");\r
+          Thread.sleep(gracefulWaitTime);\r
+          logger.info("ending to sleep...calling termination service");\r
+                 // Call Termination service\r
+                 Set<String> processInterrupted = camundaShutdown.gracefulShutdown();\r
+          returnMessage = returnMessage + " " + processInterrupted.size();\r
+                 message += String.format("processesInterrupted %s ",\r
+                       Arrays.toString(processInterrupted.toArray()));\r
+\r
+                 logger.info(message += String.format("processesInterrupted %s ",\r
+                  Arrays.toString(processInterrupted.toArray())));\r
+         } catch (InterruptedException e) {\r
+                 return "Graceful shutdown processes encountered an error.";\r
+         }\r
+         return returnMessage;\r
+  }\r
+\r
+\r
+\r
+  \r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/HealthServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/HealthServiceImpl.java
new file mode 100644 (file)
index 0000000..180f722
--- /dev/null
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.HealthService;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.runtime.Incident;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class HealthServiceImpl implements HealthService {\r
+\r
+  private HealthServiceImpl() {\r
+    // prohibit instantiation\r
+  }\r
+\r
+  @Override\r
+  public Response getHealth() {\r
+    return ResponseUtility.Build.okRequestWithMessage("UP");\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/ProcessInstanceCompletionServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/ProcessInstanceCompletionServiceImpl.java
new file mode 100644 (file)
index 0000000..66e262b
--- /dev/null
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.common.model.local.OTFProcessInstanceCompletionResponse;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.ProcessInstanceCompletionService;\r
+import java.util.List;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.HistoryService;\r
+import org.camunda.bpm.engine.ManagementService;\r
+import org.camunda.bpm.engine.RuntimeService;\r
+import org.camunda.bpm.engine.history.*;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class ProcessInstanceCompletionServiceImpl implements ProcessInstanceCompletionService {\r
+\r
+    RuntimeService runtimeService;\r
+\r
+    ManagementService managementService;\r
+\r
+    HistoryService historyService;\r
+\r
+\r
+\r
+    private ProcessInstanceCompletionServiceImpl() {\r
+        // prohibit instantiation\r
+    }\r
+\r
+    @EventListener(ApplicationReadyEvent.class)\r
+    private void initialize(){\r
+        if(this.runtimeService == null){\r
+            this.runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();\r
+        }\r
+        if(this.managementService == null){\r
+            this.managementService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getManagementService();\r
+        }\r
+        if(this.historyService == null){\r
+            this.historyService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getHistoryService();\r
+        }\r
+\r
+    }\r
+\r
+    @Override\r
+    public Response isProcessInstanceComplete(String processInstanceId) {\r
+\r
+        HistoricProcessInstance historicProcessInstance = historyService\r
+                .createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();\r
+\r
+        List<HistoricActivityInstance> historicActivityInstance = historyService\r
+                .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();\r
+\r
+        List<HistoricIncident> historicIncident =\r
+                historyService.createHistoricIncidentQuery().processInstanceId(processInstanceId).list();\r
+\r
+        List<HistoricJobLog> historicJobLog =\r
+                historyService.createHistoricJobLogQuery().processInstanceId(processInstanceId).list();\r
+\r
+        List<HistoricExternalTaskLog> historicExternalTaskLog =\r
+                historyService.createHistoricExternalTaskLogQuery().processInstanceId(processInstanceId).list();\r
+\r
+        List<HistoricVariableInstance> historicVariableInstance =\r
+                historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();\r
+\r
+\r
+\r
+        OTFProcessInstanceCompletionResponse response = new OTFProcessInstanceCompletionResponse();\r
+        response.setHistoricProcessInstance(historicProcessInstance);\r
+        response.setHistoricActivityInstance(historicActivityInstance);\r
+        response.setHistoricIncident(historicIncident);\r
+        response.setHistoricJobLog(historicJobLog);\r
+        response.setHistoricExternalTaskLog(historicExternalTaskLog);\r
+        response.setHistoricVariableInstance(historicVariableInstance);\r
+\r
+\r
+        return ResponseUtility.Build.okRequestWithObject(response);\r
+\r
+        //             Boolean done = runtimeService\r
+//                     .createProcessInstanceQuery()\r
+//                     .processInstanceId(processInstanceId)\r
+//                     .singleResult() == null;\r
+//\r
+//             if(done) {\r
+//                     return Response.ok(new ProcessInstanceCompletionResponse("Completed", "Process Instance Completed Execution")).build();\r
+//             }\r
+//\r
+//\r
+//             Incident incident = runtimeService.createIncidentQuery().processInstanceId(processInstanceId).singleResult();\r
+//             if(incident != null && incident.getIncidentType().equals("failedJob")) {\r
+//                     String errorMessage = incident.getIncidentMessage();\r
+//                     return Response.ok(new ProcessInstanceCompletionResponse("Failed", errorMessage)).build();\r
+//             }\r
+//\r
+//\r
+//             else {\r
+//                     return Response.ok(new ProcessInstanceCompletionResponse("In Progress", "Process Instance is active")).build();\r
+//             }\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/TestControlUnitServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/TestControlUnitServiceImpl.java
new file mode 100644 (file)
index 0000000..a607d5c
--- /dev/null
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.camunda.delegate.otf.common.runnable.AsynchronousTestInstanceCallable;\r
+import org.oran.otf.camunda.delegate.otf.common.runnable.SynchronousTestInstanceCallable;\r
+import org.oran.otf.camunda.exception.WorkflowProcessorException;\r
+import org.oran.otf.camunda.workflow.WorkflowProcessor;\r
+import org.oran.otf.camunda.workflow.WorkflowRequest;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.repository.TestInstanceRepository;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.TestControlUnitService;\r
+import com.fasterxml.jackson.core.type.TypeReference;\r
+import java.io.IOException;\r
+import javax.ws.rs.core.Context;\r
+import javax.ws.rs.core.HttpHeaders;\r
+import javax.ws.rs.core.MultivaluedMap;\r
+import javax.ws.rs.core.Response;\r
+import org.bson.types.ObjectId;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.stereotype.Service;\r
+import org.springframework.web.bind.annotation.RequestHeader;\r
+\r
+@Service\r
+public class TestControlUnitServiceImpl implements TestControlUnitService {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(TestControlUnitServiceImpl.class);\r
+\r
+  @Autowired\r
+  TestInstanceRepository testInstanceRepository;\r
+\r
+  @Autowired\r
+  TestExecutionRepository testExecutionRepository;\r
+\r
+  @Autowired\r
+  MongoTemplate mongoOperation;\r
+\r
+  @Autowired\r
+  WorkflowProcessor processor;\r
+\r
+  @Override\r
+  public Response executeByTestInstanceId(String testInstanceId) {\r
+    try {\r
+      TestInstance testInstance = testInstanceRepository.findById(testInstanceId).orElse(null);\r
+      if (testInstance == null) {\r
+        return Response.status(404).entity("Test Instance not found.").build();\r
+      }\r
+\r
+      WorkflowRequest req = new WorkflowRequest();\r
+      req.setAsync(false);\r
+      req.setExecutorId(new ObjectId("5cb72a7e10ba2a0042e6282a"));\r
+      req.setTestInstanceId(testInstance.get_id());\r
+      req.setTestData(testInstance.getTestData());\r
+      req.setVthInput(testInstance.getVthInput());\r
+      req.setPfloInput(testInstance.getPfloInput());\r
+      req.setMaxExecutionTimeInMillis(testInstance.getMaxExecutionTimeInMillis());\r
+      return processWorkflowRequest(req);\r
+    } catch (Exception e) {\r
+      return ResponseUtility.Build.internalServerErrorWithMessage(e.getMessage());\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public Response executeByWorkflowRequest(String workflowRequestJson) {\r
+    try {\r
+      WorkflowRequest workflowRequest =\r
+          Convert.jsonToObject(workflowRequestJson, new TypeReference<WorkflowRequest>() {\r
+          });\r
+\r
+      return processWorkflowRequest(workflowRequest);\r
+    } catch (IOException e) {\r
+      logger.error(e.getMessage());\r
+      return ResponseUtility.Build.badRequestWithMessage(e.getMessage());\r
+    }\r
+  }\r
+\r
+  private Response processWorkflowRequest(WorkflowRequest request) {\r
+    TestExecution testExecution = null;\r
+    int statusCode = 200;\r
+    try {\r
+      if (request.isAsync()) {\r
+        AsynchronousTestInstanceCallable asynchronousTestInstanceCallable =\r
+            new AsynchronousTestInstanceCallable(\r
+                request, testExecutionRepository, processor, mongoOperation);\r
+        testExecution = asynchronousTestInstanceCallable.call();\r
+      } else {\r
+        SynchronousTestInstanceCallable synchronousTestInstanceCallable =\r
+            new SynchronousTestInstanceCallable(\r
+                request, testExecutionRepository, processor, mongoOperation);\r
+        testExecution = synchronousTestInstanceCallable.call();\r
+      }\r
+    } catch (WorkflowProcessorException e) {\r
+      testExecution = e.getWorkflowResponse().getTestExecution();\r
+      statusCode = e.getWorkflowResponse().getMessageCode();\r
+    }\r
+    return ResponseUtility.Build.genericWithMessage(statusCode, testExecution.toString());\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/service/impl/TestDefinitionDeploymentServiceImpl.java b/otf-camunda/src/main/java/org/oran/otf/service/impl/TestDefinitionDeploymentServiceImpl.java
new file mode 100644 (file)
index 0000000..e7f932c
--- /dev/null
@@ -0,0 +1,134 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.service.impl;\r
+\r
+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;\r
+import org.oran.otf.common.model.local.OTFDeploymentResponse;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.service.TestDefinitionDeploymentService;\r
+import java.io.InputStream;\r
+import java.util.List;\r
+import java.util.zip.ZipInputStream;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.camunda.bpm.BpmPlatform;\r
+import org.camunda.bpm.engine.RepositoryService;\r
+import org.camunda.bpm.engine.impl.persistence.entity.DeploymentEntity;\r
+import org.camunda.bpm.engine.repository.ProcessDefinition;\r
+import org.camunda.bpm.model.bpmn.Bpmn;\r
+import org.camunda.bpm.model.bpmn.BpmnModelInstance;\r
+import org.camunda.bpm.model.xml.instance.DomElement;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.context.event.ApplicationReadyEvent;\r
+import org.springframework.context.event.EventListener;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class TestDefinitionDeploymentServiceImpl implements TestDefinitionDeploymentService {\r
+\r
+    private static Logger logger = LoggerFactory.getLogger(TestDefinitionDeploymentServiceImpl.class);\r
+\r
+\r
+    private RepositoryService repositoryService;\r
+\r
+    private TestDefinitionDeploymentServiceImpl() {\r
+        // prohibit instantiation\r
+    }\r
+\r
+    @EventListener(ApplicationReadyEvent.class)\r
+    private void initialize(){\r
+        if(this.repositoryService == null){\r
+            this.repositoryService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRepositoryService();\r
+        }\r
+    }\r
+\r
+    public Response deployTestStrategyWithResources(InputStream bpmn, InputStream resourcesZip) {\r
+\r
+        if (bpmn == null) {\r
+            logger.error("no bpmn file provided with name 'bpmn' in multipart form");\r
+            return ResponseUtility.Build.badRequestWithMessage("No bpmn file provided with name 'bpmn' in multipart form");\r
+        }\r
+        DeploymentEntity deployment = null;\r
+        try {\r
+            InputStream processDefinitionStream = bpmn;\r
+            BpmnModelInstance bpmnModelInstance = null;\r
+            try {\r
+                bpmnModelInstance = Bpmn.readModelFromStream(processDefinitionStream);\r
+                Bpmn.validateModel(bpmnModelInstance);\r
+            }\r
+            catch(Exception e){\r
+                e.printStackTrace();\r
+                return ResponseUtility.Build.badRequestWithMessage("Unable to deploy BPMN: " + e.getMessage());\r
+            }\r
+            String namespace = bpmnModelInstance.getDefinitions().getDomElement().getNamespaceURI();\r
+            List<DomElement> bpmnProcess =\r
+                    bpmnModelInstance.getDocument().getElementsByNameNs(namespace, "process");\r
+            if (bpmnProcess.size() != 1) {\r
+                logger.info("Invalid number of bpmn process tags");\r
+                return ResponseUtility.Build.internalServerErrorWithMessage("Invalid number of bpmn process tags");\r
+            } else {\r
+                String processDefinitionKey = bpmnProcess.get(0).getAttribute("id");\r
+                if (resourcesZip == null) {\r
+                    deployment = (DeploymentEntity) repositoryService.createDeployment()\r
+                            .addModelInstance(processDefinitionKey + ".bpmn", bpmnModelInstance).deploy();\r
+                } else {\r
+                    ZipInputStream zis = new ZipInputStream(resourcesZip);\r
+\r
+                    deployment = (DeploymentEntity) repositoryService.createDeployment()\r
+                            .addModelInstance(processDefinitionKey + ".bpmn", bpmnModelInstance)\r
+                            .addZipInputStream(zis).deploy();\r
+                }\r
+            }\r
+        } catch (Exception e) {\r
+            logger.info("Error: Creating Deployment: " + e.getMessage());\r
+            return ResponseUtility.Build.internalServerErrorWithMessage("Error: Creating Deployment: " + e.getMessage());\r
+        }\r
+        return Response.ok(generateResponseFromDeployment(deployment)).build();\r
+    }\r
+\r
+    @Override\r
+    public Response isProcessDefinitionDeployed(String processDefinitionKey) {\r
+        try {\r
+            ProcessDefinition definition =\r
+                    repositoryService\r
+                            .createProcessDefinitionQuery()\r
+                            .processDefinitionKey(processDefinitionKey)\r
+                            .latestVersion()\r
+                            .singleResult();\r
+            if (definition != null) {\r
+                return ResponseUtility.Build.okRequest();\r
+            }\r
+            return ResponseUtility.Build.notFound();\r
+        }\r
+        catch (Exception e){\r
+            return ResponseUtility.Build.internalServerErrorWithMessage(e.getMessage());\r
+        }\r
+    }\r
+\r
+    private OTFDeploymentResponse generateResponseFromDeployment(DeploymentEntity deployment) {\r
+        if (deployment == null) {\r
+            return new OTFDeploymentResponse(null, null, null, -1);\r
+        }\r
+        String deploymentId = deployment.getId();\r
+        String id = deployment.getDeployedProcessDefinitions().get(0).getId();\r
+        String key = deployment.getDeployedProcessDefinitions().get(0).getKey();\r
+        int version = deployment.getDeployedProcessDefinitions().get(0).getVersion();\r
+        return new OTFDeploymentResponse(deploymentId, key, id, version);\r
+    }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/spring/configuration/HttpSecurityConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/HttpSecurityConfiguration.java
new file mode 100644 (file)
index 0000000..56b5901
--- /dev/null
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.spring.configuration;\r
+\r
+import org.apache.catalina.Context;\r
+import org.apache.catalina.connector.Connector;\r
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;\r
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.context.properties.EnableConfigurationProperties;\r
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;\r
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+\r
+@Configuration\r
+@EnableConfigurationProperties\r
+public class HttpSecurityConfiguration {\r
+    @Value("${security.server.port.http}")\r
+    private int httpPort;\r
+\r
+    @Value("${security.server.port}")\r
+    private int httpsPort;\r
+\r
+    @Value("${security.https-only}")\r
+    private boolean httpsOnly;\r
+    @Bean\r
+    public ServletWebServerFactory servletContainer() {\r
+        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {\r
+            @Override\r
+            protected void postProcessContext(Context context) {\r
+                SecurityConstraint securityConstraint = new SecurityConstraint();\r
+                if(httpsOnly){ securityConstraint.setUserConstraint("CONFIDENTIAL");}\r
+                SecurityCollection collection = new SecurityCollection();\r
+                collection.addPattern("/*");\r
+                securityConstraint.addCollection(collection);\r
+                context.addConstraint(securityConstraint);\r
+            }\r
+        };\r
+        tomcat.addAdditionalTomcatConnectors(redirectConnector());\r
+        return tomcat;\r
+    }\r
+\r
+    private Connector redirectConnector() {\r
+        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");\r
+        connector.setScheme("http");\r
+        connector.setPort(httpPort);\r
+        connector.setSecure(false);\r
+        if(httpsOnly) { connector.setRedirectPort(httpsPort); }\r
+        return connector;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-camunda/src/main/java/org/oran/otf/spring/configuration/JerseyConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/JerseyConfiguration.java
new file mode 100644 (file)
index 0000000..487a8da
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.spring.configuration;\r
+\r
+import org.oran.otf.service.impl.DeleteProcessInstanceServiceImpl;\r
+import org.oran.otf.service.impl.DeleteTestDefinitionServiceImpl;\r
+import org.oran.otf.service.impl.DeveloperServiceImpl;\r
+import org.oran.otf.service.impl.HealthServiceImpl;\r
+import org.oran.otf.service.impl.ProcessInstanceCompletionServiceImpl;\r
+import org.oran.otf.service.impl.TestControlUnitServiceImpl;\r
+import org.oran.otf.service.impl.TestDefinitionDeploymentServiceImpl;\r
+\r
+import java.util.logging.Logger;\r
+import org.glassfish.jersey.logging.LoggingFeature;\r
+import org.glassfish.jersey.media.multipart.MultiPartFeature;\r
+import org.glassfish.jersey.server.ResourceConfig;\r
+import org.glassfish.jersey.server.ServerProperties;\r
+import org.glassfish.jersey.servlet.ServletContainer;\r
+import org.glassfish.jersey.servlet.ServletProperties;\r
+import org.springframework.boot.web.servlet.ServletRegistrationBean;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+\r
+/*\r
+ * Note: JerseyAutoConfiguration is used to incorporate camunda rest api In this configuration class\r
+ * we create a new servletregistrationbean to serve at /service/* while camunda serves at /rest/*\r
+ */\r
+@Configuration\r
+public class JerseyConfiguration {\r
+\r
+  private static final Logger logger = Logger.getLogger(JerseyConfiguration.class.getName());\r
+\r
+  @Bean\r
+  public ServletRegistrationBean<ServletContainer> applicationJersey() {\r
+    ServletRegistrationBean<ServletContainer> applicationJersey =\r
+        new ServletRegistrationBean<>(new ServletContainer(new ApplicationJerseyConfig()));\r
+    applicationJersey.addUrlMappings("/otf/*");\r
+    applicationJersey.setName("Open Test Framework - Test Control Unit");\r
+    applicationJersey.setLoadOnStartup(0);\r
+    return applicationJersey;\r
+  }\r
+\r
+  public class ApplicationJerseyConfig extends ResourceConfig {\r
+\r
+    public ApplicationJerseyConfig() {\r
+      register(MultiPartFeature.class);\r
+//      register(\r
+//          new OTFLoggingFeature(\r
+//              Logger.getLogger(getClass().getName()),\r
+//              Level.INFO,\r
+//              LoggingFeature.Verbosity.PAYLOAD_ANY,\r
+//              8192));\r
+\r
+      logger.info("Registering REST resources.");\r
+      register(TestControlUnitServiceImpl.class);\r
+      register(HealthServiceImpl.class);\r
+      register(DeleteTestDefinitionServiceImpl.class);\r
+      register(ProcessInstanceCompletionServiceImpl.class);\r
+      register(TestDefinitionDeploymentServiceImpl.class);\r
+      register(DeleteProcessInstanceServiceImpl.class);\r
+      register(DeveloperServiceImpl.class);\r
+\r
+      property(ServletProperties.FILTER_FORWARD_ON_404, true);\r
+      property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);\r
+      register(new LoggingFeature(logger));\r
+      logger.info("Finished registering REST resources.");\r
+    }\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFLoggingFeature.java b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFLoggingFeature.java
new file mode 100644 (file)
index 0000000..36bfb51
--- /dev/null
@@ -0,0 +1,242 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.spring.configuration;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.FilterOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.net.URI;\r
+import java.nio.charset.Charset;\r
+import java.util.ArrayList;\r
+import java.util.Base64;\r
+import java.util.List;\r
+import java.util.Objects;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+import javax.ws.rs.WebApplicationException;\r
+import javax.ws.rs.client.ClientRequestContext;\r
+import javax.ws.rs.client.ClientRequestFilter;\r
+import javax.ws.rs.client.ClientResponseContext;\r
+import javax.ws.rs.client.ClientResponseFilter;\r
+import javax.ws.rs.container.ContainerRequestContext;\r
+import javax.ws.rs.container.ContainerRequestFilter;\r
+import javax.ws.rs.container.ContainerResponseContext;\r
+import javax.ws.rs.container.ContainerResponseFilter;\r
+import javax.ws.rs.core.FeatureContext;\r
+import javax.ws.rs.core.MultivaluedMap;\r
+import javax.ws.rs.ext.WriterInterceptor;\r
+import javax.ws.rs.ext.WriterInterceptorContext;\r
+import org.glassfish.jersey.logging.LoggingFeature;\r
+import org.glassfish.jersey.message.MessageUtils;\r
+\r
+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,\r
+        ClientRequestFilter, ClientResponseFilter, WriterInterceptor {\r
+\r
+    private static final boolean printEntity = true;\r
+    private static final int maxEntitySize = 8 * 1024;\r
+    private final Logger logger = Logger.getLogger("OTFLoggingFeature");\r
+    private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();\r
+    private static final String NOTIFICATION_PREFIX = "* ";\r
+    private static final String REQUEST_PREFIX = "> ";\r
+    private static final String RESPONSE_PREFIX = "< ";\r
+    private static final String AUTHORIZATION = "Authorization";\r
+    private static final String EQUAL = " = ";\r
+    private static final String HEADERS_SEPARATOR = ", ";\r
+    private static List<String> requestHeaders;\r
+\r
+    static {\r
+        requestHeaders = new ArrayList<>();\r
+        requestHeaders.add(AUTHORIZATION);\r
+    }\r
+\r
+    public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {\r
+        super(logger, level, verbosity, maxEntitySize);\r
+    }\r
+\r
+    @Override\r
+    public boolean configure(FeatureContext context) {\r
+        context.register(this);\r
+        return true;\r
+    }\r
+\r
+    private Object getEmail(Object authorization){\r
+        try{\r
+            String encoded = ((String) authorization).split(" ")[1];\r
+            String decoded =  new String(Base64.getDecoder().decode(encoded));\r
+            return decoded.split(":")[0];\r
+        }\r
+        catch (Exception e){\r
+            return authorization;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ClientRequestContext context) {\r
+        final StringBuilder b = new StringBuilder();\r
+        printHeaders(b, context.getStringHeaders());\r
+        printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());\r
+\r
+        if (printEntity && context.hasEntity()) {\r
+            final OutputStream stream = new LoggingStream(b, context.getEntityStream());\r
+            context.setEntityStream(stream);\r
+            context.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+            // not calling log(b) here - it will be called by the interceptor\r
+        } else {\r
+            log(b);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {\r
+        final StringBuilder b = new StringBuilder();\r
+        printResponseLine(b, "Client response received", responseContext.getStatus());\r
+\r
+        if (printEntity && responseContext.hasEntity()) {\r
+            responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),\r
+                    MessageUtils.getCharset(responseContext.getMediaType())));\r
+        }\r
+        log(b);\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ContainerRequestContext context) throws IOException {\r
+        final StringBuilder b = new StringBuilder();\r
+        printHeaders(b, context.getHeaders());\r
+        printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());\r
+\r
+        if (printEntity && context.hasEntity()) {\r
+            context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));\r
+        }\r
+        log(b);\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {\r
+        final StringBuilder b = new StringBuilder();\r
+        printResponseLine(b, "Server responded with a response", responseContext.getStatus());\r
+\r
+        if (printEntity && responseContext.hasEntity()) {\r
+            final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());\r
+            responseContext.setEntityStream(stream);\r
+            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+            // not calling log(b) here - it will be called by the interceptor\r
+        } else {\r
+            log(b);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {\r
+        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);\r
+        writerInterceptorContext.proceed();\r
+        if (stream != null) {\r
+            log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));\r
+        }\r
+    }\r
+\r
+    private static class LoggingStream extends FilterOutputStream {\r
+        private final StringBuilder b;\r
+        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\r
+\r
+        LoggingStream(final StringBuilder b, final OutputStream inner) {\r
+            super(inner);\r
+\r
+            this.b = b;\r
+        }\r
+\r
+        StringBuilder getStringBuilder(Charset charset) {\r
+            // write entity to the builder\r
+            final byte[] entity = byteArrayOutputStream.toByteArray();\r
+\r
+            b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));\r
+            if (entity.length > maxEntitySize) {\r
+                b.append("...more...");\r
+            }\r
+            b.append('\n');\r
+\r
+            return b;\r
+        }\r
+\r
+        public void write(final int i) throws IOException {\r
+            if (byteArrayOutputStream.size() <= maxEntitySize) {\r
+                byteArrayOutputStream.write(i);\r
+            }\r
+            out.write(i);\r
+        }\r
+    }\r
+\r
+    private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {\r
+        for (String header : requestHeaders) {\r
+            if (Objects.nonNull(headers.get(header))) {\r
+                if(header.equalsIgnoreCase("Authorization")){\r
+                    b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);\r
+                }\r
+                else{\r
+                    b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);\r
+                }\r
+            }\r
+        }\r
+        int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);\r
+        if (lastIndex != -1) {\r
+            b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());\r
+            b.append("\n");\r
+        }\r
+    }\r
+\r
+    private void log(final StringBuilder b) {\r
+        String message = b.toString();\r
+        if (logger != null) {\r
+            logger.info(message);\r
+        }\r
+    }\r
+\r
+    private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {\r
+        b.append(NOTIFICATION_PREFIX)\r
+                .append(note)\r
+                .append(" on thread ").append(Thread.currentThread().getId())\r
+                .append(REQUEST_PREFIX).append(method).append(" ")\r
+                .append(uri.toASCIIString()).append("\n");\r
+    }\r
+\r
+    private void printResponseLine(final StringBuilder b, final String note, final int status) {\r
+        b.append(NOTIFICATION_PREFIX)\r
+                .append(note)\r
+                .append(" on thread ").append(Thread.currentThread().getId())\r
+                .append(RESPONSE_PREFIX)\r
+                .append(Integer.toString(status))\r
+                .append("\n");\r
+    }\r
+\r
+    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {\r
+        if (!stream.markSupported()) {\r
+            stream = new BufferedInputStream(stream);\r
+        }\r
+        stream.mark(maxEntitySize + 1);\r
+        final byte[] entity = new byte[maxEntitySize + 1];\r
+        final int entitySize = stream.read(entity);\r
+        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));\r
+        if (entitySize > maxEntitySize) {\r
+            b.append("...more...");\r
+        }\r
+        b.append('\n');\r
+        stream.reset();\r
+        return stream;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFMongoConfiguration.java b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFMongoConfiguration.java
new file mode 100644 (file)
index 0000000..665823f
--- /dev/null
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.spring.configuration;\r
+\r
+import com.mongodb.MongoClient;\r
+import com.mongodb.MongoClientOptions;\r
+import com.mongodb.MongoCredential;\r
+import com.mongodb.ServerAddress;\r
+import java.util.ArrayList;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;\r
+\r
+@Configuration\r
+@EnableMongoRepositories(basePackages = "org.oran.otf.common.repository")\r
+public class OTFMongoConfiguration extends AbstractMongoConfiguration {\r
+  @Value("${otf.mongo.hosts}")\r
+  private String hosts;\r
+\r
+  @Value("${otf.mongo.username}")\r
+  private String username;\r
+\r
+  @Value("${otf.mongo.password}")\r
+  private String password;\r
+\r
+  @Value("${otf.mongo.replica-set}")\r
+  private String replicaSet;\r
+\r
+  @Value("${otf.mongo.database}")\r
+  private String database;\r
+\r
+  @Override\r
+  protected String getDatabaseName() {\r
+    return database;\r
+  }\r
+\r
+  @Override\r
+  public MongoClient mongoClient() {\r
+    MongoCredential credential =\r
+        MongoCredential.createScramSha1Credential(username, database, password.toCharArray());\r
+\r
+    MongoClientOptions options =\r
+        MongoClientOptions.builder().sslEnabled(false).requiredReplicaSetName(replicaSet).build();\r
+\r
+    String[] hostArray = hosts.split(",");\r
+    ArrayList<ServerAddress> hosts = new ArrayList<>();\r
+\r
+    for (String host : hostArray) {\r
+      String[] hostSplit = host.split(":");\r
+      hosts.add(new ServerAddress(hostSplit[0], Integer.parseInt(hostSplit[1])));\r
+    }\r
+\r
+    return new MongoClient(hosts, credential, options);\r
+  }\r
+\r
+  @Override\r
+  public @Bean\r
+  MongoTemplate mongoTemplate() {\r
+    return new MongoTemplate(mongoClient(), database);\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/resources/META-INF/processes.xml.off b/otf-camunda/src/main/resources/META-INF/processes.xml.off
new file mode 100644 (file)
index 0000000..77e5a39
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<process-application\r
+  xmlns="http://www.camunda.org/schema/1.0/ProcessApplication"\r
+>\r
+\r
+  <process-archive>\r
+    <properties>\r
+      <property name="isDeleteUponUndeploy">false</property>\r
+      <property name="isScanForProcessDefinitions">true</property>\r
+    </properties>\r
+  </process-archive>\r
+\r
+</process-application>
\ No newline at end of file
diff --git a/otf-camunda/src/main/resources/META-INF/securityFilterRules.json b/otf-camunda/src/main/resources/META-INF/securityFilterRules.json
new file mode 100644 (file)
index 0000000..ef7694c
--- /dev/null
@@ -0,0 +1,52 @@
+{\r
+  "pathFilter": {\r
+    "deniedPaths": [\r
+      {\r
+        "path": "/camunda/api/engine/.*",\r
+        "methods": "*"\r
+      },\r
+      {\r
+        "path": "/camunda/api/cockpit/.*",\r
+        "methods": "*"\r
+      },\r
+      {\r
+        "path": "/camunda/app/tasklist/{engine}/.*",\r
+        "methods": "*"\r
+      },\r
+      {\r
+        "path": "/camunda/app/cockpit/{engine}/.*",\r
+        "methods": "*"\r
+      }\r
+    ],\r
+    "allowedPaths": [\r
+      {\r
+        "path": "/camunda/api/engine/engine/",\r
+        "methods": "GET"\r
+      },\r
+      {\r
+        "path": "/camunda/api/{app:cockpit}/plugin/{engine}/static/.*",\r
+        "methods": "GET"\r
+      },\r
+      {\r
+        "path": "/camunda/api/{app:cockpit}/plugin/{plugin}/{engine}/.*",\r
+        "methods": "*",\r
+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer"\r
+      },\r
+      {\r
+        "path": "/camunda/api/engine/engine/{engine}/.*",\r
+        "methods": "*",\r
+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer"\r
+      },\r
+      {\r
+        "path": "/camunda/app/{app:cockpit}/{engine}/.*",\r
+        "methods": "*",\r
+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer"\r
+      },\r
+      {\r
+        "path": "/camunda/app/{app:tasklist}/{engine}/.*",\r
+        "methods": "*",\r
+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer"\r
+      }\r
+    ]\r
+  }\r
+}\r
diff --git a/otf-camunda/src/main/resources/application.yaml b/otf-camunda/src/main/resources/application.yaml
new file mode 100644 (file)
index 0000000..bf92302
--- /dev/null
@@ -0,0 +1,108 @@
+otf:\r
+  environment: ${ENV}\r
+  mode: 'debug'\r
+  camunda:\r
+    bpm:\r
+      admin-user:\r
+        id: 'username'\r
+        password: 'password'\r
+        firstName: 'firstname'\r
+    executor:\r
+      async.core-pool-size: 50\r
+      async.max-pool-size: 400\r
+      async.queue-capacity: 25\r
+    external-task-client:\r
+      retry-limit: 0\r
+      fetch-interval-ms: 1000\r
+      lock-duration-ms: 43200000\r
+      max-tasks: 10\r
+      worker-id: 'otf-camunda-etw'\r
+    graceful-shutdown:\r
+      wait-time: 300000\r
+    executors-active: ${EXECUTORS_ACTIVE}\r
+    mysql:\r
+      url: jdbc:mysql://${OTF_CAMUNDA_DB_URL}?useSSL=false&serverTimezone=UTC #&logger=com.mysql.cj.log.Slf4JLogger&profileSQL=true\r
+      username: ${OTF_CAMUNDA_DB_USERNAME}\r
+      password: ${OTF_CAMUNDA_DB_PASSWORD}\r
+  cadi:\r
+    enabled: true\r
+    aaf-mech-id: ${AAF_ID}\r
+    aaf-mech-password: ${AAF_MECH_PASSWORD}\r
+    aaf-perm-type: ${AAF_PERM_TYPE}\r
+    hostname: ${CADI_HOSTNAME}\r
+    keyfile: ${CADI_KEYFILE}\r
+    aaf-call-timeout: 10000\r
+    aaf-conn-timeout: 6000\r
+    aaf-default-realm: 'localhost'\r
+    aaf-env: 'PROD'\r
+    aaf-locate-url: 'https://localhost'\r
+    aaf-lur-class: 'org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm'\r
+    aaf-url: 'https://localhost'\r
+    basic-realm: 'localhost'\r
+    basic-warn: true\r
+    cadi-latitude: '38.62782'\r
+    cadi-logLevel: '16384'\r
+    cadi-longitude: '-90.19458'\r
+    cadi-protocols: 'TLSv1.1,TLSv1.2'\r
+\r
+  mongo:\r
+    hosts: ${OTF_MONGO_HOSTS}\r
+    username: ${OTF_MONGO_USERNAME}\r
+    password: ${OTF_MONGO_PASSWORD}\r
+    replica-set: ${OTF_MONGO_REPLICASET}\r
+    database: ${OTF_MONGO_DATABASE}\r
+  ssl:\r
+    key-store-type: 'PKCS12'\r
+    keystore-path: ${OTF_CERT_PATH}\r
+    keystore-password: ${OTF_CERT_PASS}\r
+\r
+#https://stackoverflow.com/questions/50387638/spring-boot-jersey-type-filter-bad-request-400-for-service-consumes-multipar/50423639#50423639\r
+spring.jersey.filter.order: -100000\r
+spring.main.allow-bean-definition-overriding: true\r
+server:\r
+  port: 8443\r
+  port.http: 8000\r
+  tomcat.max-threads: 800\r
+#  ssl:\r
+    key-store-type: 'PKCS12'\r
+    key-store: ${OTF_CERT_PATH}\r
+    key-store-password: ${OTF_CERT_PASS}\r
+security:\r
+  https-only: true\r
+  require-ssl: false\r
+  server.port: 8443\r
+  server.port.http: 8080\r
+#  server.tomcat.max-threads=800\r
+#  security.require-ssl=true\r
+#  server.ssl.key-store-type=PKCS12\r
+#  server.ssl.key-store=${OTF_CERT_PATH}\r
+#  server.ssl.key-store-password=${OTF_CERT_PASS}\r
+\r
+camunda.bpm.job-execution.enabled: true\r
+camunda.bpm.job-execution.queueSize: 25\r
+camunda.bpm.job-execution.corePoolSize: 50\r
+camunda.bpm.job-execution.maxPoolSize: 400\r
+#camunda.bpm.job-execution.max-jobs-per-acquisition: 99\r
+\r
+camunda.bpm.database.schema-update: true\r
+logging:\r
+  level:\r
+    com.zaxxer.hikari: DEBUG\r
+\r
+logging.file.max-history: 5\r
+logging.file: otf/logs/camunda.log\r
+logging.path: otf/logs\r
+\r
+#logging:\r
+#  level:\r
+#    org.camunda.bpm.engine.jobexecutor: OFF\r
+#    org.camunda.bpm.engine.context: OFF\r
+#    org.camunda.bpm.extension.reactor.projectreactor: OFF\r
+    #org.camunda.bpm.extension.reactor.projectreactor.routing.ConsumerFilterRouter: OFF\r
+    #org.camunda.bpm.extension.reactor.projectreactor: INFO\r
+    #org.camunda.engine.ProcessEngineException: INFO\r
+    #org.camunda.bpm.cfg: DEBUG\r
+    #org.camunda.bpm.engine.impl.persistence.entity.JobEntity: DEBUG\r
+    #org.camunda.bpm.engine.cmd: DEBUG\r
+    #org.springframework.web: DEBUG\r
+    #org.camunda.bpm.engine.rest: DEBUG\r
diff --git a/otf-camunda/src/main/resources/banner.txt b/otf-camunda/src/main/resources/banner.txt
new file mode 100644 (file)
index 0000000..7a62038
--- /dev/null
@@ -0,0 +1,12 @@
+                                             U  ___ u   _____     _____\r
+                                            \/"_ \/  |_ " _|   |" ___|\r
+                                            | | | |    | |    U| |_  u\r
+                                          .-,_| |_| |   /| |\   \|  _|/\r
+                                           \_)-\___/   u |_|U    |_|\r
+                                              \\     _// \\_   )(\\,-\r
+                                             (__)   (__) (__) (__)(_/\r
+\r
+       Open Test Framework: (Blitzcrank)\r
+       Spring-Boot:  (v2.1.4.RELEASE)\r
+       Camunda BPM: (v7.10.4-ee)\r
+       Camunda BPM Spring Boot Starter: (v3.2.0)\r
diff --git a/otf-camunda/src/main/resources/bpmn/pingGoogleDNS.bpmn b/otf-camunda/src/main/resources/bpmn/pingGoogleDNS.bpmn
new file mode 100644 (file)
index 0000000..7ef5852
--- /dev/null
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_0nye5hw" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="2.0.3">\r
+  <bpmn:process id="pingGoogleDns" name="Ping Google DNS" isExecutable="true">\r
+    <bpmn:startEvent id="StartEvent_1r2e4pd" camunda:asyncBefore="true">\r
+      <bpmn:outgoing>SequenceFlow_1gpkkbm</bpmn:outgoing>\r
+    </bpmn:startEvent>\r
+    <bpmn:endEvent id="EndEvent_0czvyun">\r
+      <bpmn:incoming>SequenceFlow_1psgifi</bpmn:incoming>\r
+      <bpmn:terminateEventDefinition id="TerminateEventDefinition_12nqmmc" />\r
+    </bpmn:endEvent>\r
+    <bpmn:sequenceFlow id="SequenceFlow_1gpkkbm" sourceRef="StartEvent_1r2e4pd" targetRef="Task_06bcfeo" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_1psgifi" sourceRef="Task_10nhde5" targetRef="EndEvent_0czvyun" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_054puyx" sourceRef="Task_1r783jz" targetRef="Task_10nhde5" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_12x2s0z" sourceRef="Task_06bcfeo" targetRef="Task_1r783jz" />\r
+    <bpmn:scriptTask id="Task_06bcfeo" name="Set Parameters" scriptFormat="javascript">\r
+      <bpmn:incoming>SequenceFlow_1gpkkbm</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_12x2s0z</bpmn:outgoing>\r
+      <bpmn:script>var vthInput = {\r
+       vthInput: {\r
+               Task_1r783jz: {\r
+                       testData: {\r
+                               targetHost: "8.8.8.8",\r
+                               useJumpServer: false\r
+                       },\r
+                       vthName: "Unused parameter",\r
+                       testConfig: {}\r
+               }\r
+       }\r
+};\r
+\r
+execution.setVariable("vthInput", JSON.stringify(vthInput));</bpmn:script>\r
+    </bpmn:scriptTask>\r
+    <bpmn:serviceTask id="Task_1r783jz" name="VTH:PING TEST" camunda:delegateExpression="${callTestHeadDelegate}">\r
+      <bpmn:incoming>SequenceFlow_12x2s0z</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_054puyx</bpmn:outgoing>\r
+    </bpmn:serviceTask>\r
+    <bpmn:serviceTask id="Task_10nhde5" name="UTIL:LogTestResult" camunda:delegateExpression="${logTestResultDelegate}">\r
+      <bpmn:incoming>SequenceFlow_054puyx</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_1psgifi</bpmn:outgoing>\r
+    </bpmn:serviceTask>\r
+  </bpmn:process>\r
+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">\r
+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="pingGoogleDns">\r
+      <bpmndi:BPMNShape id="StartEvent_1r2e4pd_di" bpmnElement="StartEvent_1r2e4pd">\r
+        <dc:Bounds x="167" y="117" width="36" height="36" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="416" y="153" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="EndEvent_0czvyun_di" bpmnElement="EndEvent_0czvyun">\r
+        <dc:Bounds x="880" y="117" width="36" height="36" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="1117" y="165" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_1gpkkbm_di" bpmnElement="SequenceFlow_1gpkkbm">\r
+        <di:waypoint x="203" y="135" />\r
+        <di:waypoint x="312" y="135" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="441.5" y="114" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_1psgifi_di" bpmnElement="SequenceFlow_1psgifi">\r
+        <di:waypoint x="789" y="135" />\r
+        <di:waypoint x="880" y="135" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="1165" y="133" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_054puyx_di" bpmnElement="SequenceFlow_054puyx">\r
+        <di:waypoint x="607" y="135" />\r
+        <di:waypoint x="689" y="135" />\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_12x2s0z_di" bpmnElement="SequenceFlow_12x2s0z">\r
+        <di:waypoint x="412" y="135" />\r
+        <di:waypoint x="507" y="135" />\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNShape id="ScriptTask_0anmrwm_di" bpmnElement="Task_06bcfeo">\r
+        <dc:Bounds x="312" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="ServiceTask_1dnlrl2_di" bpmnElement="Task_1r783jz">\r
+        <dc:Bounds x="507" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="ServiceTask_04caqpo_di" bpmnElement="Task_10nhde5">\r
+        <dc:Bounds x="689" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+    </bpmndi:BPMNPlane>\r
+  </bpmndi:BPMNDiagram>\r
+</bpmn:definitions>\r
diff --git a/otf-camunda/src/main/resources/mail-config.properties b/otf-camunda/src/main/resources/mail-config.properties
new file mode 100644 (file)
index 0000000..38c3819
--- /dev/null
@@ -0,0 +1,7 @@
+# send mails via SMTP\r
+mail.transport.protocol=smtp\r
+\r
+mail.smtp.host=localhost\r
+mail.smtp.port=25\r
+mail.smtp.auth=false\r
+mail.smtp.ssl.enable=false
\ No newline at end of file
diff --git a/otf-camunda/src/test/java/org/oran/otf/api/tests/config/DataConfig.java b/otf-camunda/src/test/java/org/oran/otf/api/tests/config/DataConfig.java
new file mode 100644 (file)
index 0000000..2307939
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.config;\r
+\r
+public class DataConfig {\r
+\r
+}\r
diff --git a/otf-camunda/src/test/java/org/oran/otf/api/tests/config/InMemoryConfig.java b/otf-camunda/src/test/java/org/oran/otf/api/tests/config/InMemoryConfig.java
new file mode 100644 (file)
index 0000000..06e8464
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.config;\r
+\r
+public class InMemoryConfig {\r
+\r
+}\r
diff --git a/otf-camunda/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java b/otf-camunda/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java
new file mode 100644 (file)
index 0000000..edb8b72
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.shared;\r
+\r
+public class MemoryDatabase {\r
+\r
+}\r
diff --git a/otf-camunda/src/test/java/org/oran/otf/api/tests/unit/common/utility/http/HeadersUtilityTest.java b/otf-camunda/src/test/java/org/oran/otf/api/tests/unit/common/utility/http/HeadersUtilityTest.java
new file mode 100644 (file)
index 0000000..ae0e133
--- /dev/null
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.common.utility.http;\r
+\r
+import org.oran.otf.common.utility.http.HeadersUtility;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.Assert;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.mockito.junit.MockitoJUnitRunner;\r
+\r
+@RunWith(MockitoJUnitRunner.class)\r
+public class HeadersUtilityTest {\r
+  public Map<String, String> headers;\r
+\r
+  @Before\r
+  public void setup(){\r
+    headers = new HashMap<>();\r
+    headers.put("GET", "/some/random/route/exmaple");\r
+    headers.put("Host", "localhost");\r
+    headers.put("Authorization", "Basic som3R4ndOmStringK3y");\r
+    headers.put("User-Agent", "James Bond");\r
+    headers.put("Accept", "*/*");\r
+\r
+  }\r
+  @Test\r
+  public void authIsMasked(){\r
+\r
+    System.out.println("Authorization header in format 'Basic: key' will mask the auth with 4 *");\r
+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);\r
+    Assertions.assertThat(maskedAuth.get("Authorization")).isEqualTo("Basic ****");\r
+  }\r
+  @Test\r
+  public void originalHeadersDidNotChange(){\r
+    System.out.println("Make sure HeaderUtility.maskAuth() does not change the map passed to function");\r
+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);\r
+    Assertions.assertThat(headers.get("Authorization")).isEqualTo("Basic som3R4ndOmStringK3y");\r
+  }\r
+\r
+  @Test\r
+  public void noAuthHeadersPassed(){\r
+    System.out.println("Make sure HeaderUtility.maskAuth() works if headers are missing a Authorization field");\r
+    headers.remove("Authorization");\r
+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);\r
+    Assertions.assertThat(maskedAuth).isEqualTo(headers);\r
+  }\r
+\r
+  @Test\r
+  public void authKeyFormatHasNoSpace(){\r
+    System.out.println("Make sure HeaderUtility.maskAuth() works if Authorization does not follow format 'Authorization: [Type] [Key]'");\r
+    headers.put("Authorization", "Basicsom3R4ndOmStringK3y");\r
+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);\r
+    Assertions.assertThat(maskedAuth.get("Authorization")).isEqualTo("****");\r
+  }\r
+\r
+}\r
diff --git a/otf-camunda/src/test/resources/application-test.properties b/otf-camunda/src/test/resources/application-test.properties
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/otf-frontend/.dockerignore b/otf-frontend/.dockerignore
new file mode 100644 (file)
index 0000000..ae84271
--- /dev/null
@@ -0,0 +1 @@
+./node_modules
\ No newline at end of file
diff --git a/otf-frontend/.gitignore b/otf-frontend/.gitignore
new file mode 100644 (file)
index 0000000..91cebbb
--- /dev/null
@@ -0,0 +1,121 @@
+\r
+# Created by https://www.gitignore.io/api/node,angular\r
+\r
+### Angular ###\r
+## Angular ##\r
+# compiled output\r
+/dist\r
+/tmp\r
+/app/**/*.js\r
+/app/**/*.js.map\r
+package-lock.json\r
+\r
+# dependencies\r
+/node_modules\r
+/bower_components\r
+\r
+# IDEs and editors\r
+/.idea\r
+/.vscode\r
+\r
+# misc\r
+/.sass-cache\r
+/connect.lock\r
+/coverage/*\r
+/libpeerconnection.log\r
+npm-debug.log\r
+testem.log\r
+/typings\r
+\r
+# e2e\r
+/e2e/*.js\r
+/e2e/*.map\r
+\r
+#System Files\r
+.DS_Store\r
+\r
+### Node ###\r
+# Logs\r
+logs\r
+*.log\r
+npm-debug.log*\r
+yarn-debug.log*\r
+yarn-error.log*\r
+\r
+# Runtime data\r
+pids\r
+*.pid\r
+*.seed\r
+*.pid.lock\r
+\r
+# Directory for instrumented libs generated by jscoverage/JSCover\r
+lib-cov\r
+\r
+# Coverage directory used by tools like istanbul\r
+coverage\r
+\r
+# nyc test coverage\r
+.nyc_output\r
+\r
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\r
+.grunt\r
+\r
+# Bower dependency directory (https://bower.io/)\r
+bower_components\r
+\r
+# node-waf configuration\r
+.lock-wscript\r
+\r
+# Compiled binary addons (https://nodejs.org/api/addons.html)\r
+build/Release\r
+\r
+# Dependency directories\r
+node_modules/\r
+jspm_packages/\r
+\r
+# TypeScript v1 declaration files\r
+typings/\r
+\r
+# Optional npm cache directory\r
+.npm\r
+\r
+# Optional eslint cache\r
+.eslintcache\r
+\r
+# Optional REPL history\r
+.node_repl_history\r
+\r
+# Output of 'npm pack'\r
+*.tgz\r
+\r
+# Yarn Integrity file\r
+.yarn-integrity\r
+\r
+# dotenv environment variables file\r
+.env\r
+\r
+# parcel-bundler cache (https://parceljs.org/)\r
+.cache\r
+\r
+# next.js build output\r
+.next\r
+\r
+# nuxt.js build output\r
+.nuxt\r
+\r
+# vuepress build output\r
+.vuepress/dist\r
+\r
+# Serverless directories\r
+.serverless\r
+\r
+\r
+# End of https://www.gitignore.io/api/node,angular\r
+server/config/default.json\r
+\r
+# certs\r
+server/config/cert/*.pem\r
+server/config/cert/otf.pem\r
+server/config/cert/privateKey.pem\r
+# env script\r
+envScript.sh\r
diff --git a/otf-frontend/Dockerfile b/otf-frontend/Dockerfile
new file mode 100644 (file)
index 0000000..a3fd845
--- /dev/null
@@ -0,0 +1,31 @@
+FROM node:8.16-alpine\r
+\r
+ENV ENV=development\r
+ENV NAMESPACE=namespace\r
+ENV APP_NAME=otf-frontend\r
+ENV APP_VERSION=1.0\r
+ENV OTF_URL=https://loaclhost:32524/\r
+ENV OTF_EMAIL=email@email.com\r
+ENV AUTHENTICATION_SECRET=/ytoYB+iD5HUuDLmeqStcoUPwqw=\r
+ENV SERVICEAPI_URL=https://localhost:32303/otf/api/\r
+ENV SERVICEAPI_URIEXECUTETESTINSTANCE=testInstance/execute/v1/id/\r
+ENV SERVICEAPI_AAFID=username\r
+ENV SERVICEAPI_AAFPASSWORD=password\r
+ENV CAMUNDAAPI_URL=https://localhost:31313/\r
+ENV CAMUNDAAPI_AAFID=username\r
+ENV CAMUNDAAPI_AAFPASSWORD=password\r
+ENV MONGO_BASEURL=localhost:27017/\r
+ENV MONGO_DBOTF=otf\r
+ENV MONGO_REPLICASET=mongoOTF\r
+ENV MONGO_USERNAME=username\r
+ENV MONGO_PASSWORD=password\r
+\r
+COPY . /home/node\r
+WORKDIR /home/node\r
+\r
+RUN mkdir -p /otf/logs\r
+\r
+RUN npm install --unsafe-perm\r
+RUN npm run-script build\r
+\r
+ENTRYPOINT [ "npm", "start" ]\r
diff --git a/otf-frontend/Jenkinsfile b/otf-frontend/Jenkinsfile
new file mode 100644 (file)
index 0000000..0cc1a01
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env groovy\r
+\r
+properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"],\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"],\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "username"],\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"],\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"],\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'OTF_CAMUNDA_DB', defaultValue: "otf_camunda_dev_db"],\r
+[$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: ""]\r
+]]])\r
+\r
+echo "Build branch: ${env.BRANCH_NAME}"\r
+\r
+node("docker"){\r
+       stage 'Checkout'\r
+       checkout scm\r
+       PHASES=PHASE.tokenize( '_' );\r
+       echo "PHASES : " + PHASES\r
+\r
+\r
+       ARTIFACT_ID="otf-frontend";\r
+       VERSION="Camille.1.0.3";\r
+       //TODO: deal with namespace and docker registry\r
+       NAMESPACE=""\r
+       DOCKER_REGISTRY=""\r
+\r
+       if( ENV.equalsIgnoreCase("dev") ){\r
+               IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + "/" + ARTIFACT_ID +  ":" + VERSION\r
+       }\r
+       if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){\r
+               IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID +  ":" + VERSION\r
+       }\r
+       if( ENV.equalsIgnoreCase("st") ){\r
+               IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID +  ":" + VERSION\r
+       }\r
+       echo "Artifact: " + IMAGE_NAME\r
+\r
+       withEnv(["PATH=${env.PATH}:${env.WORKSPACE}/linux-amd64", "HELM_HOME=${env.WORKSPACE}"]) {\r
+\r
+               echo "PATH=${env.PATH}"\r
+               echo "HELM_HOME=${env.HELM_HOME}"\r
+\r
+               withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {\r
+\r
+                       // sh """\r
+                       //      docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD\r
+                       // """\r
+\r
+                       if (PHASES.contains("BUILD")){\r
+                               stage 'Publish Artifact'\r
+\r
+\r
+                                       echo "Artifact: " + IMAGE_NAME\r
+\r
+                                       sh """\r
+                                               docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD\r
+                                               docker build --no-cache -t $IMAGE_NAME .\r
+                                               docker push $IMAGE_NAME\r
+                                       """\r
+\r
+\r
+                       }\r
+               }\r
+\r
+               if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY")) {\r
+\r
+                       stage 'Init Helm'\r
+\r
+                       //check if helm exists if not install\r
+                       if(fileExists('linux-amd64/helm')){\r
+                               sh """\r
+                                       echo "helm is already installed"\r
+                               """\r
+                       }\r
+                       else{\r
+                       //download helm\r
+                               sh """\r
+                                       echo "installing helm"\r
+                                       wget  https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz\r
+                                       tar -xf helm-v2.8.2-linux-amd64.tar.gz\r
+                                       rm helm-v2.8.2-linux-amd64.tar.gz\r
+                               """\r
+                       }\r
+\r
+                       withCredentials([file(credentialsId: KUBE_CONFIG,  variable: 'KUBECONFIG')]) {\r
+\r
+                               dir('helm'){\r
+                               //check if charts are valid, and then perform dry run, if successful then upgrade/install charts\r
+\r
+                                       if (PHASES.contains("UNDEPLOY") ) {\r
+                                               stage 'Undeploy'\r
+\r
+                                               sh """\r
+                                                       helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID\r
+                                               """\r
+                                       }\r
+\r
+                               //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace\r
+                       if (PHASES.contains("DEPLOY") ){\r
+                               stage 'Deploy'\r
+                                                       withCredentials([\r
+                                                               usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME_MONGO', passwordVariable: 'PASSWORD_MONGO'),\r
+                                                               usernamePassword(credentialsId: 'FEATHERS_AUTH', usernameVariable: 'USER', passwordVariable: 'AUTHENTICATION_SECRET')\r
+                                                               ]) {\r
+\r
+                                                               sh """\r
+                                                                       echo "Validate Yaml"\r
+                                                                       helm lint $ARTIFACT_ID\r
+\r
+                                                                       echo "View Helm Templates"\r
+                                                                       helm template $ARTIFACT_ID \\r
+                                                                               --set appName=$ARTIFACT_ID \\r
+                                                                               --set version=$VERSION \\r
+                                                                               --set image=$IMAGE_NAME \\r
+                                                                               --set namespace=$TILLER_NAMESPACE \\r
+                                                                         --set env=$ENV \\r
+                                                                               --set AUTHENTICATION_SECRET=$AUTHENTICATION_SECRET \\r
+                                                                               --set mongo.username=$USERNAME_MONGO \\r
+                                                                               --set mongo.password=$PASSWORD_MONGO\r
+\r
+                                                                       echo "Perform Dry Run Of Install"\r
+                                                                       helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \\r
+                                                                               --set appName=$ARTIFACT_ID \\r
+                                                                               --set version=$VERSION \\r
+                                                                               --set image=$IMAGE_NAME \\r
+                                                                               --set namespace=$TILLER_NAMESPACE \\r
+                                                                         --set env=$ENV \\r
+                                                                               --set AUTHENTICATION_SECRET=$AUTHENTICATION_SECRET \\r
+                                                                               --set mongo.username=$USERNAME_MONGO \\r
+                                                                               --set mongo.password=$PASSWORD_MONGO\r
+\r
+                                                                       echo "Helm Install/Upgrade"\r
+                                                               helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --timeout 1000 $ARTIFACT_ID $ARTIFACT_ID \\r
+                                                                               --set appName=$ARTIFACT_ID \\r
+                                                                               --set version=$VERSION \\r
+                                                                               --set image=$IMAGE_NAME \\r
+                                                                               --set namespace=$TILLER_NAMESPACE \\r
+                                                                               --set env=$ENV \\r
+                                                                               --set AUTHENTICATION_SECRET=$AUTHENTICATION_SECRET \\r
+                                                                               --set mongo.username=$USERNAME_MONGO \\r
+                                                                               --set mongo.password=$PASSWORD_MONGO\r
+\r
+                                                               """\r
+                                                       }\r
+                       }\r
+\r
+                               }\r
+                       }\r
+       }\r
+\r
+       }\r
+}\r
diff --git a/otf-frontend/LICENSES.txt b/otf-frontend/LICENSES.txt
new file mode 100644 (file)
index 0000000..695ac56
--- /dev/null
@@ -0,0 +1,28 @@
+Unless otherwise specified, all software contained herein is licensed\r
+under the Apache License, Version 2.0 (the "Software License");\r
+you may not use this software except in compliance with the Software\r
+License. You may obtain a copy of the Software License at\r
+\r
+http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+Unless required by applicable law or agreed to in writing, software\r
+distributed under the Software License is distributed on an "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+See the Software License for the specific language governing permissions\r
+and limitations under the Software License.\r
+\r
+\r
+\r
+Unless otherwise specified, all documentation contained herein is licensed\r
+under the Creative Commons License, Attribution 4.0 Intl. (the\r
+"Documentation License"); you may not use this documentation except in\r
+compliance with the Documentation License. You may obtain a copy of the\r
+Documentation License at\r
+\r
+https://creativecommons.org/licenses/by/4.0/\r
+\r
+Unless required by applicable law or agreed to in writing, documentation\r
+distributed under the Documentation License is distributed on an "AS IS"\r
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+implied. See the Documentation License for the specific language governing\r
+permissions and limitations under the Documentation License.\r
diff --git a/otf-frontend/angular.json b/otf-frontend/angular.json
new file mode 100644 (file)
index 0000000..53e96b5
--- /dev/null
@@ -0,0 +1,147 @@
+{\r
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\r
+  "version": 1,\r
+  "newProjectRoot": "projects",\r
+  "projects": {\r
+    "test-ng4": {\r
+      "root": "",\r
+      "sourceRoot": "client/src",\r
+      "projectType": "application",\r
+      "architect": {\r
+        "build": {\r
+          "builder": "@angular-devkit/build-angular:browser",\r
+          "options": {\r
+            "outputPath": "client/dist",\r
+            "index": "client/src/index.html",\r
+            "main": "client/src/main.ts",\r
+            "tsConfig": "client/src/tsconfig.app.json",\r
+            "polyfills": "client/src/polyfills.ts",\r
+            "assets": [\r
+              "client/src/assets",\r
+              "client/src/favicon.ico"\r
+            ], \r
+            "scripts": [\r
+              "node_modules/jquery/dist/jquery.js",\r
+              "node_modules/datatables.net/js/jquery.dataTables.js"\r
+            ],\r
+            "styles": [\r
+              "node_modules/font-awesome/css/font-awesome.css",\r
+              "node_modules/material-design-icons/iconfont/material-icons.css",\r
+              "client/src/styles/app.scss",\r
+              "node_modules/bpmn-js/dist/assets/diagram-js.css",\r
+              "node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css",\r
+              "node_modules/bpmn-js-properties-panel/styles/properties.less",\r
+              "node_modules/bpmn-font/dist/css/bpmn.css",\r
+              "node_modules/diagram-js-minimap/assets/diagram-js-minimap.css",\r
+              "node_modules/datatables.net-dt/css/jquery.dataTables.css"\r
+            ]\r
+          },\r
+          "configurations": {\r
+            "production": {\r
+              "optimization": false,\r
+              "outputHashing": "all",\r
+              "sourceMap": false,\r
+              "extractCss": true,\r
+              "namedChunks": false,\r
+              "aot": false,\r
+              "extractLicenses": true,\r
+              "vendorChunk": false,\r
+              "buildOptimizer": false,\r
+              "fileReplacements": [\r
+                {\r
+                  "replace": "client/src/environments/environment.ts",\r
+                  "with": "client/src/environments/environment.prod.ts"\r
+                }\r
+              ]\r
+            }\r
+          }\r
+        },\r
+        "serve": {\r
+          "builder": "@angular-devkit/build-angular:dev-server",\r
+          "options": {\r
+            "browserTarget": "test-ng4:build"\r
+          },\r
+          "configurations": {\r
+            "production": {\r
+              "browserTarget": "test-ng4:build:production"\r
+            }\r
+          }\r
+        },\r
+        "extract-i18n": {\r
+          "builder": "@angular-devkit/build-angular:extract-i18n",\r
+          "options": {\r
+            "browserTarget": "test-ng4:build"\r
+          }\r
+        },\r
+        "test": {\r
+          "builder": "@angular-devkit/build-angular:karma",\r
+          "options": {\r
+            "main": "client/src/test.ts",\r
+            "karmaConfig": "./karma.conf.js",\r
+            "polyfills": "client/src/polyfills.ts",\r
+            "tsConfig": "client/src/tsconfig.spec.json",\r
+            "scripts": [\r
+              "node_modules/chart.js/dist/Chart.js"\r
+            ],\r
+            "styles": [\r
+              "node_modules/font-awesome/css/font-awesome.css",\r
+              "node_modules/material-design-icons/iconfont/material-icons.css",\r
+              "client/src/styles/app.scss"\r
+            ],\r
+            "assets": [\r
+              "client/src/assets",\r
+              "client/src/favicon.ico"\r
+            ]\r
+          }\r
+        },\r
+        "lint": {\r
+          "builder": "@angular-devkit/build-angular:tslint",\r
+          "options": {\r
+            "tsConfig": [\r
+              "client/src/tsconfig.app.json",\r
+              "client/src/tsconfig.spec.json"\r
+            ],\r
+            "exclude": [\r
+              "**/node_modules/**"\r
+            ]\r
+          }\r
+        }\r
+      }\r
+    },\r
+    "test-ng4-e2e": {\r
+      "root": "",\r
+      "sourceRoot": "",\r
+      "projectType": "application",\r
+      "architect": {\r
+        "e2e": {\r
+          "builder": "@angular-devkit/build-angular:protractor",\r
+          "options": {\r
+            "protractorConfig": "./protractor.conf.js",\r
+            "devServerTarget": "test-ng4:serve"\r
+          }\r
+        },\r
+        "lint": {\r
+          "builder": "@angular-devkit/build-angular:tslint",\r
+          "options": {\r
+            "tsConfig": [\r
+              "client/e2e/tsconfig.e2e.json"\r
+            ],\r
+            "exclude": [\r
+              "**/node_modules/**"\r
+            ]\r
+          }\r
+        }\r
+      }\r
+    }\r
+  },\r
+  "defaultProject": "test-ng4",\r
+  "schematics": {\r
+    "@schematics/angular:component": {\r
+      "prefix": "app",\r
+      "styleext": "scss"\r
+    },\r
+    "@schematics/angular:directive": {\r
+      "prefix": "app"\r
+    }\r
+  }\r
+}\r
diff --git a/otf-frontend/client/.gitignore b/otf-frontend/client/.gitignore
new file mode 100644 (file)
index 0000000..f94aed4
--- /dev/null
@@ -0,0 +1,45 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.\r
+\r
+# compiled output\r
+/tmp\r
+/out-tsc\r
+/dist\r
+/test\r
+\r
+# dependencies\r
+/node_modules\r
+/test\r
+package-lock.json\r
+\r
+# IDEs and editors\r
+/.idea\r
+.project\r
+.classpath\r
+.c9/\r
+*.launch\r
+.settings/\r
+*.sublime-workspace\r
+\r
+# IDE - VSCode\r
+.vscode/*\r
+!.vscode/settings.json\r
+!.vscode/tasks.json\r
+!.vscode/launch.json\r
+!.vscode/extensions.json\r
+\r
+# misc\r
+/.sass-cache\r
+/connect.lock\r
+/coverage\r
+/libpeerconnection.log\r
+npm-debug.log\r
+testem.log\r
+/typings\r
+\r
+# e2e\r
+/e2e/*.js\r
+/e2e/*.map\r
+\r
+# System Files\r
+.DS_Store\r
+Thumbs.db\r
diff --git a/otf-frontend/client/config/.editorconfig b/otf-frontend/client/config/.editorconfig
new file mode 100644 (file)
index 0000000..a0e8f5a
--- /dev/null
@@ -0,0 +1,13 @@
+# Editor configuration, see http://editorconfig.org\r
+root = true\r
+\r
+[*]\r
+charset = utf-8\r
+indent_style = space\r
+indent_size = 4\r
+insert_final_newline = true\r
+trim_trailing_whitespace = true\r
+\r
+[*.md]\r
+max_line_length = off\r
+trim_trailing_whitespace = false\r
diff --git a/otf-frontend/client/config/.travis.yml b/otf-frontend/client/config/.travis.yml
new file mode 100644 (file)
index 0000000..01e3108
--- /dev/null
@@ -0,0 +1,14 @@
+language: node_js\r
+node_js:\r
+  - '9'\r
+  - '10'\r
+\r
+install:\r
+  - npm install\r
+\r
+script:\r
+  - npm run test-ci\r
+\r
+cache:\r
+  directories:\r
+  - node_modules\r
diff --git a/otf-frontend/client/config/karma.conf.js b/otf-frontend/client/config/karma.conf.js
new file mode 100644 (file)
index 0000000..3be39c4
--- /dev/null
@@ -0,0 +1,50 @@
+// Karma configuration file, see link for more information\r
+// https://karma-runner.github.io/1.0/config/configuration-file.html\r
+\r
+module.exports = function (config) {\r
+  const defaults = {\r
+    basePath: '',\r
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],\r
+    plugins: [\r
+      require('karma-jasmine'),\r
+      require('karma-chrome-launcher'),\r
+      require('karma-jasmine-html-reporter'),\r
+      require('karma-coverage-istanbul-reporter'),\r
+      require('@angular-devkit/build-angular/plugins/karma')\r
+    ],\r
+    client:{\r
+      clearContext: false // leave Jasmine Spec Runner output visible in browser\r
+    },\r
+    coverageIstanbulReporter: {\r
+      dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],\r
+      fixWebpackSourcePaths: true\r
+    },\r
+    angularCli: {\r
+      environment: 'dev'\r
+    },\r
+    reporters: ['progress', 'kjhtml'],\r
+    port: 9876,\r
+    colors: true,\r
+    logLevel: config.LOG_INFO,\r
+    autoWatch: true,\r
+    browsers: ['Chrome'],\r
+    singleRun: false,\r
+  }\r
+\r
+  if (process.env.TEST_CI) {\r
+    Object.assign(defaults, {\r
+      autoWatch: false,\r
+      browsers: ['ChromeHeadlessNoSandbox'],\r
+      singleRun: true,\r
+      customLaunchers: {\r
+        ChromeHeadlessNoSandbox: {\r
+          base: 'ChromeHeadless',\r
+          flags: ['--no-sandbox']\r
+        }\r
+      },\r
+      browserNoActivityTimeout: 60000,\r
+    })\r
+  }\r
+\r
+  config.set(defaults)\r
+};\r
diff --git a/otf-frontend/client/config/protractor.conf.js b/otf-frontend/client/config/protractor.conf.js
new file mode 100644 (file)
index 0000000..b1a56c1
--- /dev/null
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information\r
+// https://github.com/angular/protractor/blob/master/lib/config.ts\r
+\r
+const { SpecReporter } = require('jasmine-spec-reporter');\r
+\r
+exports.config = {\r
+  allScriptsTimeout: 11000,\r
+  specs: [\r
+    './e2e/**/*.e2e-spec.ts'\r
+  ],\r
+  capabilities: {\r
+    'browserName': 'chrome'\r
+  },\r
+  directConnect: true,\r
+  baseUrl: 'http://localhost:4200/',\r
+  framework: 'jasmine',\r
+  jasmineNodeOpts: {\r
+    showColors: true,\r
+    defaultTimeoutInterval: 30000,\r
+    print: function() {}\r
+  },\r
+  onPrepare() {\r
+    require('ts-node').register({\r
+      project: 'e2e/tsconfig.e2e.json'\r
+    });\r
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\r
+  }\r
+};\r
diff --git a/otf-frontend/client/config/tsconfig.json b/otf-frontend/client/config/tsconfig.json
new file mode 100644 (file)
index 0000000..bcd2543
--- /dev/null
@@ -0,0 +1,19 @@
+{\r
+  "compileOnSave": false,\r
+  "compilerOptions": {\r
+    "outDir": "./dist/out-tsc",\r
+    "sourceMap": true,\r
+    "declaration": false,\r
+    "moduleResolution": "node",\r
+    "emitDecoratorMetadata": true,\r
+    "experimentalDecorators": true,\r
+    "target": "es5",\r
+    "typeRoots": [\r
+      "node_modules/@types"\r
+    ],\r
+    "lib": [\r
+      "es2017",\r
+      "dom"\r
+    ]\r
+  }\r
+}\r
diff --git a/otf-frontend/client/config/tslint.json b/otf-frontend/client/config/tslint.json
new file mode 100644 (file)
index 0000000..9e1157b
--- /dev/null
@@ -0,0 +1,139 @@
+{\r
+  "rulesDirectory": [\r
+    "../../node_modules/codelyzer"\r
+  ],\r
+  "rules": {\r
+    "arrow-return-shorthand": true,\r
+    "callable-types": true,\r
+    "class-name": true,\r
+    "comment-format": [\r
+      true,\r
+      "check-space"\r
+    ],\r
+    "curly": true,\r
+    "eofline": true,\r
+    "forin": true,\r
+    "import-blacklist": [\r
+      true\r
+    ],\r
+    "import-spacing": true,\r
+    "indent": [\r
+      true,\r
+      "spaces"\r
+    ],\r
+    "interface-over-type-literal": true,\r
+    "label-position": true,\r
+    "max-line-length": [\r
+      true,\r
+      140\r
+    ],\r
+    "member-access": false,\r
+    "member-ordering": [\r
+      true,\r
+      {\r
+        "order": [\r
+          "static-field",\r
+          "instance-field",\r
+          "static-method",\r
+          "instance-method"\r
+        ]\r
+      }\r
+    ],\r
+    "no-arg": true,\r
+    "no-bitwise": true,\r
+    "no-console": [\r
+      true,\r
+      "debug",\r
+      "info",\r
+      "time",\r
+      "timeEnd",\r
+      "trace"\r
+    ],\r
+    "no-construct": true,\r
+    "no-debugger": true,\r
+    "no-duplicate-super": true,\r
+    "no-empty": false,\r
+    "no-empty-interface": true,\r
+    "no-eval": true,\r
+    "no-inferrable-types": [\r
+      true,\r
+      "ignore-params"\r
+    ],\r
+    "no-misused-new": true,\r
+    "no-non-null-assertion": true,\r
+    "no-shadowed-variable": true,\r
+    "no-string-literal": false,\r
+    "no-string-throw": true,\r
+    "no-switch-case-fall-through": true,\r
+    "no-trailing-whitespace": true,\r
+    "no-unnecessary-initializer": true,\r
+    "no-unused-expression": true,\r
+    "no-use-before-declare": true,\r
+    "no-var-keyword": true,\r
+    "object-literal-sort-keys": false,\r
+    "one-line": [\r
+      true,\r
+      "check-open-brace",\r
+      "check-catch",\r
+      "check-else",\r
+      "check-whitespace"\r
+    ],\r
+    "prefer-const": true,\r
+    "quotemark": [\r
+      true,\r
+      "single"\r
+    ],\r
+    "radix": true,\r
+    "semicolon": [\r
+      true,\r
+      "always"\r
+    ],\r
+    "triple-equals": [\r
+      true,\r
+      "allow-null-check"\r
+    ],\r
+    "typedef-whitespace": [\r
+      true,\r
+      {\r
+        "call-signature": "nospace",\r
+        "index-signature": "nospace",\r
+        "parameter": "nospace",\r
+        "property-declaration": "nospace",\r
+        "variable-declaration": "nospace"\r
+      }\r
+    ],\r
+    "typeof-compare": true,\r
+    "unified-signatures": true,\r
+    "variable-name": false,\r
+    "whitespace": [\r
+      true,\r
+      "check-branch",\r
+      "check-decl",\r
+      "check-operator",\r
+      "check-separator",\r
+      "check-type"\r
+    ],\r
+    "directive-selector": [\r
+      true,\r
+      "attribute",\r
+      "app",\r
+      "camelCase"\r
+    ],\r
+    "component-selector": [\r
+      true,\r
+      "element",\r
+      "app",\r
+      "kebab-case"\r
+    ],\r
+    "use-input-property-decorator": true,\r
+    "use-output-property-decorator": true,\r
+    "use-host-property-decorator": true,\r
+    "no-input-rename": true,\r
+    "no-output-rename": true,\r
+    "use-life-cycle-interface": true,\r
+    "use-pipe-transform-interface": true,\r
+    "component-class-suffix": true,\r
+    "directive-class-suffix": true,\r
+    "invoke-injectable": true\r
+  }\r
+}\r
diff --git a/otf-frontend/client/e2e/app.e2e-spec.ts b/otf-frontend/client/e2e/app.e2e-spec.ts
new file mode 100644 (file)
index 0000000..64bb730
--- /dev/null
@@ -0,0 +1,14 @@
+import { AppPage } from './app.po';\r
+\r
+describe('test-ng4 App', () => {\r
+  let page: AppPage;\r
+\r
+  beforeEach(() => {\r
+    page = new AppPage();\r
+  });\r
+\r
+  it('should display welcome message', () => {\r
+    page.navigateTo();\r
+    expect(page.getParagraphText()).toEqual('SB Admin BS4 Angular5');\r
+  });\r
+});\r
diff --git a/otf-frontend/client/e2e/app.po.ts b/otf-frontend/client/e2e/app.po.ts
new file mode 100644 (file)
index 0000000..625420f
--- /dev/null
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';\r
+\r
+export class AppPage {\r
+  navigateTo() {\r
+    return browser.get('/');\r
+  }\r
+\r
+  getParagraphText() {\r
+    return element(by.css('app-root h1')).getText();\r
+  }\r
+}\r
diff --git a/otf-frontend/client/e2e/tsconfig.e2e.json b/otf-frontend/client/e2e/tsconfig.e2e.json
new file mode 100644 (file)
index 0000000..a7da750
--- /dev/null
@@ -0,0 +1,14 @@
+{\r
+  "extends": "../config/tsconfig.json",\r
+  "compilerOptions": {\r
+    "outDir": "../out-tsc/e2e",\r
+    "baseUrl": "./",\r
+    "module": "commonjs",\r
+    "target": "es5",\r
+    "types": [\r
+      "jasmine",\r
+      "jasminewd2",\r
+      "node"\r
+    ]\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied-routing.module.ts b/otf-frontend/client/src/app/access-denied/access-denied-routing.module.ts
new file mode 100644 (file)
index 0000000..23e7bcc
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { AccessDeniedComponent } from './access-denied.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '', component: AccessDeniedComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class AccessDeniedRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied.component.html b/otf-frontend/client/src/app/access-denied/access-denied.component.html
new file mode 100644 (file)
index 0000000..ba588bd
--- /dev/null
@@ -0,0 +1,19 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<p>\r
+  access-denied works!\r
+</p>\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied.component.scss b/otf-frontend/client/src/app/access-denied/access-denied.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied.component.spec.ts b/otf-frontend/client/src/app/access-denied/access-denied.component.spec.ts
new file mode 100644 (file)
index 0000000..249d493
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { AccessDeniedComponent } from './access-denied.component';\r
+\r
+describe('AccessDeniedComponent', () => {\r
+  let component: AccessDeniedComponent;\r
+  let fixture: ComponentFixture<AccessDeniedComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ AccessDeniedComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(AccessDeniedComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied.component.ts b/otf-frontend/client/src/app/access-denied/access-denied.component.ts
new file mode 100644 (file)
index 0000000..070c3e9
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+\r
+@Component({\r
+  selector: 'app-access-denied',\r
+  templateUrl: './access-denied.component.html',\r
+  styleUrls: ['./access-denied.component.scss']\r
+})\r
+export class AccessDeniedComponent implements OnInit {\r
+\r
+  constructor() { }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied.module.spec.ts b/otf-frontend/client/src/app/access-denied/access-denied.module.spec.ts
new file mode 100644 (file)
index 0000000..e52e1ee
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { AccessDeniedModule } from './access-denied.module';\r
+\r
+describe('AccessDeniedModule', () => {\r
+  let accessDeniedModule: AccessDeniedModule;\r
+\r
+  beforeEach(() => {\r
+    accessDeniedModule = new AccessDeniedModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(accessDeniedModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/access-denied/access-denied.module.ts b/otf-frontend/client/src/app/access-denied/access-denied.module.ts
new file mode 100644 (file)
index 0000000..f914e1c
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { AccessDeniedRoutingModule } from './access-denied-routing.module';\r
+import { AccessDeniedComponent } from './access-denied.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    AccessDeniedRoutingModule\r
+  ],\r
+  declarations: [AccessDeniedComponent]\r
+})\r
+export class AccessDeniedModule { }\r
diff --git a/otf-frontend/client/src/app/account/account-routing.module.ts b/otf-frontend/client/src/app/account/account-routing.module.ts
new file mode 100644 (file)
index 0000000..695939d
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { AccountComponent } from './account.component';\r
+\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '', component: AccountComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class AccountRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/account/account.component.html b/otf-frontend/client/src/app/account/account.component.html
new file mode 100644 (file)
index 0000000..b7e38af
--- /dev/null
@@ -0,0 +1,25 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<div class="account-page" [@routerTransition]>\r
+    <div class="row justify-content-md-center">\r
+        <div class="col-md-4">\r
+            <img src="assets/images/NetworkLogo.jpg" width="200px" class="user-avatar" />\r
+            <h1>Open Testing Framework</h1>\r
+            <h3 id="verifyMessage">{{message}}</h3>\r
+        </div>\r
+    </div>\r
+</div>\r
diff --git a/otf-frontend/client/src/app/account/account.component.scss b/otf-frontend/client/src/app/account/account.component.scss
new file mode 100644 (file)
index 0000000..0db25da
--- /dev/null
@@ -0,0 +1,115 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+$topnav-background-color: #222;\r
+:host {\r
+  display: block;\r
+}\r
+.account-page {\r
+  position: absolute;\r
+  top: 0;\r
+  left: 0;\r
+  right: 0;\r
+  bottom: 0;\r
+  overflow: auto;\r
+  background: $topnav-background-color;\r
+  text-align: center;\r
+  color: #fff;\r
+  padding: 3em;\r
+  .col-lg-4 {\r
+    padding: 0;\r
+  }\r
+  .input-lg {\r
+    height: 46px;\r
+    padding: 10px 16px;\r
+    font-size: 18px;\r
+    line-height: 1.3333333;\r
+    border-radius: 0;\r
+  }\r
+  .input-underline {\r
+    background: 0 0;\r
+    border: none;\r
+    box-shadow: none;\r
+    border-bottom: 2px solid rgba(255, 255, 255, 0.5);\r
+    color: #fff;\r
+    border-radius: 0;\r
+  }\r
+  .input-underline:focus {\r
+    border-bottom: 2px solid #fff;\r
+    box-shadow: none;\r
+  }\r
+  .rounded-btn {\r
+    -webkit-border-radius: 50px;\r
+    border-radius: 50px;\r
+    color: rgba(255, 255, 255, 0.8);\r
+    background: $topnav-background-color;\r
+    border: 2px solid rgba(255, 255, 255, 0.8);\r
+    font-size: 18px;\r
+    line-height: 40px;\r
+    padding: 0 25px;\r
+  }\r
+  .rounded-btn:hover,\r
+  .rounded-btn:focus,\r
+  .rounded-btn:active,\r
+  .rounded-btn:visited {\r
+    color: rgba(255, 255, 255, 1);\r
+    border: 2px solid rgba(255, 255, 255, 1);\r
+    outline: none;\r
+  }\r
+\r
+  h1 {\r
+    font-weight: 300;\r
+    margin-top: 20px;\r
+    margin-bottom: 10px;\r
+    font-size: 36px;\r
+    small {\r
+      color: rgba(255, 255, 255, 0.7);\r
+    }\r
+  }\r
+\r
+  .form-group {\r
+    padding: 8px 0;\r
+    input::-webkit-input-placeholder {\r
+      color: rgba(255, 255, 255, 0.6) !important;\r
+    }\r
+\r
+    input:-moz-placeholder {\r
+      /* Firefox 18- */\r
+      color: rgba(255, 255, 255, 0.6) !important;\r
+    }\r
+\r
+    input::-moz-placeholder {\r
+      /* Firefox 19+ */\r
+      color: rgba(255, 255, 255, 0.6) !important;\r
+    }\r
+\r
+    input:-ms-input-placeholder {\r
+      color: rgba(255, 255, 255, 0.6) !important;\r
+    }\r
+  }\r
+  .form-content {\r
+    padding: 30px 0;\r
+  }\r
+  .user-avatar {\r
+    -webkit-border-radius: 50%;\r
+    border-radius: 50%;\r
+    border: 2px solid #fff;\r
+  }\r
+\r
+  #verifyMessage{\r
+    margin-top: 100px\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/account/account.component.spec.ts b/otf-frontend/client/src/app/account/account.component.spec.ts
new file mode 100644 (file)
index 0000000..b8b6b46
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { AccountComponent } from './account.component';\r
+\r
+describe('AccountComponent', () => {\r
+  let component: AccountComponent;\r
+  let fixture: ComponentFixture<AccountComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ AccountComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(AccountComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/account/account.component.ts b/otf-frontend/client/src/app/account/account.component.ts
new file mode 100644 (file)
index 0000000..a285598
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import {ActivatedRoute} from "@angular/router";\r
+import {AccountService} from "../shared/services/account.service";\r
+import { Router} from '@angular/router';\r
+import { routerTransition } from '../router.animations';\r
+\r
+\r
+@Component({\r
+  selector: 'app-account',\r
+  templateUrl: './account.component.html',\r
+  styleUrls: ['./account.component.scss'],\r
+  animations: [routerTransition()]\r
+\r
+})\r
+export class AccountComponent implements OnInit {\r
+    private action: string;\r
+    private token: string;\r
+    public message: string;\r
+  constructor(private router: Router, private route: ActivatedRoute, private accountService: AccountService) { }\r
+\r
+  ngOnInit() {\r
+      this.message = "";\r
+      this.action = this.route.snapshot.paramMap.get("action");\r
+      this.route.queryParamMap.subscribe(queryParams => {\r
+          this.token = queryParams.get("token");\r
+      });\r
+      if(this.action && this.token){\r
+          this.accountService.verify(this.token)\r
+              .subscribe(\r
+                  data  => {\r
+                      this.message = "Thanks for verifying your email. You will be notified when your account is enabled by an admin."\r
+                  },\r
+                  error  => {\r
+                      this.router.navigate(['/dashboard']);\r
+                  }\r
+              );\r
+      }\r
+      else{\r
+          this.router.navigate(['/dashboard']);\r
+      }\r
+\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/account/account.module.spec.ts b/otf-frontend/client/src/app/account/account.module.spec.ts
new file mode 100644 (file)
index 0000000..3c1abda
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { AccountModule } from './account.module';\r
+\r
+describe('AccountModule', () => {\r
+  let accountModule: AccountModule;\r
+\r
+  beforeEach(() => {\r
+    accountModule = new AccountModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(accountModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/account/account.module.ts b/otf-frontend/client/src/app/account/account.module.ts
new file mode 100644 (file)
index 0000000..c744693
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { FormsModule } from '@angular/forms';\r
+import { AccountRoutingModule } from './account-routing.module';\r
+import { AccountComponent } from './account.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    AccountRoutingModule,\r
+    FormsModule\r
+  ],\r
+  declarations: [AccountComponent]\r
+})\r
+export class AccountModule { }\r
diff --git a/otf-frontend/client/src/app/app-routing.module.ts b/otf-frontend/client/src/app/app-routing.module.ts
new file mode 100644 (file)
index 0000000..03f22df
--- /dev/null
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { AppComponent } from './app.component';\r
+import { AuthGuard } from './shared';\r
+\r
+const routes: Routes = [\r
+    { path: '', loadChildren: './layout/layout.module#LayoutModule', canActivate: [AuthGuard] },\r
+    { path: 'login', loadChildren: './login/login.module#LoginModule' },\r
+    { path: 'signup', loadChildren: './signup/signup.module#SignupModule' },\r
+    { path: 'error', loadChildren: './server-error/server-error.module#ServerErrorModule' },\r
+    { path: 'access-denied', loadChildren: './access-denied/access-denied.module#AccessDeniedModule' },\r
+    { path: 'not-found', loadChildren: './not-found/not-found.module#NotFoundModule' },\r
+    { path: 'account/:action', loadChildren: './account/account.module#AccountModule' },\r
+    { path: '**', redirectTo: 'not-found' }\r
+\r
+\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forRoot(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class AppRoutingModule {}\r
diff --git a/otf-frontend/client/src/app/app.component.html b/otf-frontend/client/src/app/app.component.html
new file mode 100644 (file)
index 0000000..9450cf2
--- /dev/null
@@ -0,0 +1,17 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<router-outlet></router-outlet>
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/app.component.scss b/otf-frontend/client/src/app/app.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/app.component.spec.ts b/otf-frontend/client/src/app/app.component.spec.ts
new file mode 100644 (file)
index 0000000..4236461
--- /dev/null
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'\r
+import { APP_BASE_HREF } from '@angular/common'\r
+\r
+import { AppComponent } from './app.component'\r
+import { AppModule } from './app.module'\r
+\r
+describe('AppComponent', () => {\r
+  let component: AppComponent\r
+  let fixture: ComponentFixture<AppComponent>\r
+\r
+  beforeEach(\r
+    async(() => {\r
+      TestBed.configureTestingModule({\r
+        imports: [AppModule],\r
+        providers: [\r
+          { provide: APP_BASE_HREF, useValue: '/' },\r
+        ]\r
+      }).compileComponents()\r
+    })\r
+  )\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(AppComponent)\r
+    component = fixture.componentInstance\r
+    fixture.detectChanges()\r
+  })\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy()\r
+  })\r
+})\r
diff --git a/otf-frontend/client/src/app/app.component.ts b/otf-frontend/client/src/app/app.component.ts
new file mode 100644 (file)
index 0000000..bdd0c1d
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { AppGlobals } from './app.global';\r
+\r
+\r
+@Component({\r
+    selector: 'app-root',\r
+    templateUrl: './app.component.html',\r
+    providers: [AppGlobals],\r
+    styleUrls: ['./app.component.scss']\r
+})\r
+export class AppComponent implements OnInit {\r
+\r
+    constructor() {\r
+        \r
+    }\r
+\r
+    ngOnInit() {\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/app.global.ts b/otf-frontend/client/src/app/app.global.ts
new file mode 100644 (file)
index 0000000..5fde648
--- /dev/null
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from "@angular/core";\r
+import { HttpHeaders } from "@angular/common/http";\r
+\r
+export class AppGlobals {\r
+    public static baseAPIUrl: string = '/otf/api/v1/';\r
+    public static version: string = 'Camille.1.0';\r
+}\r
diff --git a/otf-frontend/client/src/app/app.module.spec.ts b/otf-frontend/client/src/app/app.module.spec.ts
new file mode 100644 (file)
index 0000000..eadbb3c
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { AppModule } from './app.module';\r
+\r
+describe('AppModule', () => {\r
+    let appModule: AppModule;\r
+\r
+    beforeEach(() => {\r
+        appModule = new AppModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(appModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/app.module.ts b/otf-frontend/client/src/app/app.module.ts
new file mode 100644 (file)
index 0000000..ff1baba
--- /dev/null
@@ -0,0 +1,90 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {CommonModule} from '@angular/common';\r
+import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';\r
+import {NgModule} from '@angular/core';\r
+import {BrowserModule} from '@angular/platform-browser';\r
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';\r
+import {TranslateLoader, TranslateModule} from '@ngx-translate/core';\r
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';\r
+import {AppRoutingModule} from './app-routing.module';\r
+import {AppComponent} from './app.component';\r
+import {AuthGuard, AdminGuard, SharedPipesModule, PageHeaderModule} from './shared';\r
+import {FormsModule} from '@angular/forms';\r
+import {ListService} from './shared/services/list.service';\r
+import {MatButtonModule, MatDatepickerModule, MatDialogModule, MatIconModule, MatInputModule, MatRadioModule, MatMenu, MatMenuModule} from '@angular/material';\r
+import {AppGlobals} from './app.global';\r
+import {ErrorInterceptor} from './error.interceptor';\r
+import {CookieService} from 'ngx-cookie-service';\r
+import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker';\r
+import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';\r
+import { FeathersService } from './shared/services/feathers.service';\r
+import { CoreModule } from './core/core.module';\r
+import { AbilityModule } from '@casl/angular'\r
+\r
+\r
+const config: SocketIoConfig = { url: '/', options: {transports: ['websocket']} };\r
+\r
+// AoT requires an exported function for factories\r
+export const createTranslateLoader = (http: HttpClient) => {\r
+    /* for development\r
+    return new TranslateHttpLoader(\r
+        http,\r
+        '/start-angular/SB-Admin-BS4-Angular-6/master/dist/assets/i18n/',\r
+        '.json'\r
+    ); */\r
+    return new TranslateHttpLoader(http, './assets/i18n/', '.json');\r
+};\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        BrowserModule,\r
+        FormsModule,\r
+        PageHeaderModule,\r
+        BrowserAnimationsModule,\r
+        HttpClientModule,\r
+        TranslateModule.forRoot({\r
+            loader: {\r
+                provide: TranslateLoader,\r
+                useFactory: createTranslateLoader,\r
+                deps: [HttpClient]\r
+            }\r
+        }),\r
+        AppRoutingModule,\r
+        SharedPipesModule,\r
+        NgxMaterialTimepickerModule.forRoot(),\r
+        MatButtonModule,\r
+        MatDialogModule,\r
+        MatRadioModule,\r
+        MatInputModule,\r
+        MatIconModule,\r
+        MatDatepickerModule,\r
+        SocketIoModule.forRoot(config),\r
+        CoreModule,\r
+        MatMenuModule,\r
+        AbilityModule.forRoot()\r
+    ],\r
+    declarations: [\r
+        AppComponent,\r
+    ],\r
+    providers: [\r
+        FeathersService, {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true}, AuthGuard, AdminGuard, ListService, AppGlobals, CookieService],\r
+    bootstrap: [AppComponent]\r
+})\r
+export class AppModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/core/core.module.spec.ts b/otf-frontend/client/src/app/core/core.module.spec.ts
new file mode 100644 (file)
index 0000000..201e873
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { CoreModule } from './core.module';\r
+\r
+describe('CoreModule', () => {\r
+  let coreModule: CoreModule;\r
+\r
+  beforeEach(() => {\r
+    coreModule = new CoreModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(coreModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/core/core.module.ts b/otf-frontend/client/src/app/core/core.module.ts
new file mode 100644 (file)
index 0000000..d050a07
--- /dev/null
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { FeathersService } from 'app/shared/services/feathers.service';\r
+import { ModelService } from 'app/shared/services/model.service';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { TestHeadService } from 'app/shared/services/test-head.service';\r
+import { TestInstanceService } from 'app/shared/services/test-instance.service';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+import { AccountService } from 'app/shared/services/account.service';\r
+import { AuthService } from 'app/shared/services/auth.service';\r
+import { ExecuteService } from 'app/shared/services/execute.service';\r
+import { FeedbackService } from 'app/shared/services/feedback.service';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { FileService } from 'app/shared/services/file.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { SchedulingService } from 'app/shared/services/scheduling.service';\r
+import { UserService } from 'app/shared/services/user.service';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule\r
+  ],\r
+  providers: [\r
+    TestDefinitionService,\r
+    TestHeadService,\r
+    TestInstanceService,\r
+    TestExecutionService,\r
+    AccountService,\r
+    AuthService,\r
+    ExecuteService,\r
+    FeedbackService,\r
+    FileTransferService,\r
+    FileService,\r
+    GroupService,\r
+    SchedulingService,\r
+    UserService\r
+  ],\r
+  declarations: []\r
+})\r
+export class CoreModule { }\r
diff --git a/otf-frontend/client/src/app/error.interceptor.ts b/otf-frontend/client/src/app/error.interceptor.ts
new file mode 100644 (file)
index 0000000..85f10d0
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';\r
+import { Observable, throwError } from 'rxjs';\r
+import { catchError } from 'rxjs/operators';\r
+import { Router } from '@angular/router';\r
+import { AuthService } from './shared/services/auth.service';\r
+\r
+\r
+@Injectable()\r
+export class ErrorInterceptor implements HttpInterceptor {\r
+    constructor(private auth: AuthService, private router: Router) {}\r
+\r
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\r
+        return next.handle(request).pipe(catchError(err => {\r
+            if (err.status === 401) {\r
+                // auto logout if 401 response returned from api\r
+                this.auth.logout();\r
+                this.router.navigateByUrl('/login');\r
+            }\r
+            \r
+            const error = err.error.message || err.statusText;\r
+            return throwError(error);\r
+        }))\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/header/header.component.html b/otf-frontend/client/src/app/layout/components/header/header.component.html
new file mode 100644 (file)
index 0000000..81f8614
--- /dev/null
@@ -0,0 +1,111 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<nav class="navbar navbar-expand-lg fixed-top">\r
+        <img src="../../../../assets/images/NetworkLogo.jpg" class="rounded-circle mr-2" style="width:40px; height: 40px;" />\r
+        <a class="navbar-brand" href="#">Open Test Framework</a>\r
+        <span *ngIf="groups">\r
+            <button mat-button style="color:white" #goupMenuTrigger="matMenuTrigger" [matMenuTriggerFor]="groupMenu">{{ selectedGroup?.groupName || 'Select Group' }} <mat-icon>arrow_drop_down</mat-icon></button>\r
+            <mat-menu #groupMenu="matMenu">\r
+                <span style="margin-left: 15px; cursor: pointer; color: #007bff" (click)="createGroup()">+ New Group</span>\r
+                <span *ngFor="let group of groups">\r
+                    <!-- Handle branch node buttons here -->\r
+                    <span *ngIf="group.children && group.children.length > 0" style="z-index:1031">\r
+                        <button mat-menu-item [matMenuTriggerFor]="menu.childMenu" (click)="changeGroup(group)" [disabled]="group.disabled">\r
+                            {{group.displayName}}\r
+                        </button>\r
+                        <app-menu-item #menu [items]="group.children" (dataEvent)="changeGroup($event)"></app-menu-item>\r
+                    </span>\r
+                    <!-- Leaf node buttons here -->\r
+                    <span *ngIf="!group.children || group.children.length === 0" style="z-index:1031">\r
+                        <button mat-menu-item color="primary" (click)="changeGroup(group)">\r
+                            {{group.displayName}}\r
+                        </button>\r
+                    </span>\r
+                </span>\r
+            </mat-menu>\r
+        </span>\r
+        <button class="navbar-toggler" type="button" (click)="toggleSidebar()">\r
+            <!-- <span class="navbar-toggler-icon"></span> -->\r
+            <i class="fa fa-bars text-muted" aria-hidden="true"></i>\r
+        </button>\r
+        <div class="collapse navbar-collapse">\r
+            <ul class="navbar-nav ml-auto">\r
+                <!-- <li class="nav-item dropdown" ngbDropdown>\r
+                    <a href="javascript:void(0)" class="nav-link" ngbDropdownToggle>\r
+                        <i class="fa fa-language"></i> {{ 'Language' | translate }} <b class="caret"></b>\r
+                    </a>\r
+                    <div class="dropdown-menu-right" ngbDropdownMenu>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('en')">\r
+                            {{ 'English' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('fr')">\r
+                            {{ 'French' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('ur')">\r
+                            {{ 'Urdu' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('es')">\r
+                            {{ 'Spanish' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('it')">\r
+                            {{ 'Italian' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('fa')">\r
+                            {{ 'Farsi' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('de')">\r
+                            {{ 'German' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('zh-CHS')">\r
+                            {{ 'Simplified Chinese' | translate }}\r
+                        </a>\r
+                    </div>\r
+                </li> -->\r
+                <!--<li *ngIf="groups && selectedGroup">\r
+                    <span *ngFor="let group of groups">\r
+                        <!-- Handle branch node buttons here --\r
+                        <span *ngIf="group.children && group.children.length > 0" style="z-index:1031">\r
+                            <button mat-button [matMenuTriggerFor]="menu.childMenu" [disabled]="group.disabled">\r
+                                {{group.displayName}}\r
+                            </button>\r
+                            <app-menu-item #menu [items]="group.children"></app-menu-item>\r
+                        </span>\r
+                        <!-- Leaf node buttons here --\r
+                        <span *ngIf="!group.children || group.children.length === 0" style="z-index:1031">\r
+                            <button mat-button color="primary" (click)="group.click()">\r
+                                {{group.displayName}}\r
+                            </button>\r
+                        </span>\r
+                    </span>\r
+                </li> -->\r
+                <li class="nav-item dropdown" ngbDropdown>\r
+                    <a href="javascript:void(0)" class="nav-link" ngbDropdownToggle>\r
+                        <i class="fa fa-user"></i> {{username}} <b class="caret"></b>\r
+                    </a>\r
+                    <div class="dropdown-menu-right" ngbDropdownMenu>\r
+                        <a class="dropdown-item" [routerLink]="['/settings']" >\r
+                            <i class="fa fa-fw fa-cog"></i> {{ 'Settings' | translate }}\r
+                        </a>\r
+                        <a class="dropdown-item" [routerLink]="['/login']" (click)="onLoggedout()">\r
+                            <i class="fa fa-fw fa-power-off"></i> {{ 'Log Out' | translate }}\r
+                        </a>\r
+                    </div>\r
+                </li>\r
+            </ul>\r
+        </div>\r
+    </nav>\r
+    \r
diff --git a/otf-frontend/client/src/app/layout/components/header/header.component.scss b/otf-frontend/client/src/app/layout/components/header/header.component.scss
new file mode 100644 (file)
index 0000000..591097a
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+$topnav-background-color: #045C87;\r
+\r
+:host {\r
+    .navbar {\r
+       \r
+        background-color: $topnav-background-color;\r
+        .navbar-brand {\r
+            color: #fff;\r
+            font-size: 1.5em !important;\r
+        }\r
+        .nav-item > a {\r
+            color: #fff;\r
+            &:hover {\r
+                color: lighten($topnav-background-color, 50%);\r
+            }\r
+        }\r
+    }\r
+    .messages {\r
+        width: 300px;\r
+        .media {\r
+            border-bottom: 1px solid #ddd;\r
+            padding: 5px 10px;\r
+            &:last-child {\r
+                border-bottom: none;\r
+            }\r
+        }\r
+        .media-body {\r
+            h5 {\r
+                font-size: 13px;\r
+                font-weight: 600;\r
+            }\r
+            .small {\r
+                margin: 0;\r
+            }\r
+            .last {\r
+                font-size: 12px;\r
+                margin: 0;\r
+            }\r
+        }\r
+    }\r
+}\r
+.my-class{\r
+    line-height: 25px;\r
+    height: 25px!important;\r
+    min-height: unset!important;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/header/header.component.spec.ts b/otf-frontend/client/src/app/layout/components/header/header.component.spec.ts
new file mode 100644 (file)
index 0000000..8d9d5ef
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'\r
+import { RouterTestingModule } from '@angular/router/testing'\r
+import { TranslateModule } from '@ngx-translate/core'\r
+\r
+import { HeaderComponent } from './header.component'\r
+import { LayoutModule } from '../../layout.module'\r
+\r
+describe('HeaderComponent', () => {\r
+  let component: HeaderComponent\r
+  let fixture: ComponentFixture<HeaderComponent>\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      imports: [\r
+        LayoutModule,\r
+        RouterTestingModule,\r
+        TranslateModule.forRoot(),\r
+      ],\r
+    })\r
+    .compileComponents()\r
+  }))\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(HeaderComponent)\r
+    component = fixture.componentInstance\r
+    fixture.detectChanges()\r
+  })\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy()\r
+  })\r
+})\r
diff --git a/otf-frontend/client/src/app/layout/components/header/header.component.ts b/otf-frontend/client/src/app/layout/components/header/header.component.ts
new file mode 100644 (file)
index 0000000..924d50e
--- /dev/null
@@ -0,0 +1,205 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild } from '@angular/core';\r
+import { Router, NavigationEnd } from '@angular/router';\r
+import { TranslateService } from '@ngx-translate/core';\r
+import { AuthService } from 'app/shared/services/auth.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { UserService } from 'app/shared/services/user.service';\r
+//import { group } from '@angular/animations';\r
+import { CreateGroupModalComponent } from 'app/shared/modules/create-group-modal/create-group-modal.component';\r
+import { MatDialog } from '@angular/material/dialog';\r
+import { NavItem } from 'app/shared/components/menu-item/menu-item.component';\r
+import { MatMenuTrigger } from '@angular/material';\r
+\r
+\r
+@Component({\r
+    selector: 'app-header',\r
+    templateUrl: './header.component.html',\r
+    styleUrls: ['./header.component.scss']\r
+})\r
+\r
+export class HeaderComponent implements OnInit {\r
+    pushRightClass: string = 'push-right';\r
+    myStyle: object = {};\r
+    myParams: object = {};\r
+    width: number = 100;\r
+    height: number = 100;\r
+\r
+    public groups: Array<NavItem>;\r
+    public selectedGroup;\r
+\r
+    @ViewChild('goupMenuTrigger') groupMenu: MatMenuTrigger;\r
+    \r
+\r
+    constructor(\r
+        private translate: TranslateService,\r
+        public router: Router,\r
+        private auth: AuthService,\r
+        private cookie: CookieService,\r
+        public _groups: GroupService,\r
+        private user: UserService,\r
+        private modal: MatDialog\r
+        \r
+    ) {\r
+\r
+        this.translate.addLangs(['en', 'fr', 'ur', 'es', 'it', 'fa', 'de', 'zh-CHS']);\r
+        this.translate.setDefaultLang('en');\r
+        const browserLang = this.translate.getBrowserLang();\r
+        this.translate.use(browserLang.match(/en|fr|ur|es|it|fa|de|zh-CHS/) ? browserLang : 'en');\r
+\r
+\r
+        this.router.events.subscribe(val => {\r
+            if (\r
+                val instanceof NavigationEnd &&\r
+                window.innerWidth <= 992 &&\r
+                this.isToggled()\r
+            ) {\r
+                this.toggleSidebar();\r
+            }\r
+        });\r
+    }\r
+    public currentUser;// = {};\r
+    public username;\r
+\r
+\r
+    ngOnInit() {\r
+       \r
+       \r
+        this.currentUser = JSON.parse(this.cookie.get('currentUser'));\r
+        this.username = this.currentUser["firstName"] + " " + this.currentUser["lastName"];\r
+        \r
+       \r
+\r
+        this._groups.setUp();\r
+\r
+        this._groups.listChange().subscribe(res => {\r
+            this.groups = res;\r
+        });\r
+       \r
+        // if (!window.localStorage.getItem("currentGroupId"))\r
+        // {\r
+        //     if (!(this.currentUser.defaultGroup)){\r
+        //         let userPatch = {\r
+        //             _id : this.currentUser._id,\r
+        //            defaultGroup : this.currentUser.groups[0].groupId,\r
+        //            defaultGroupEnabled: false  \r
+        //         };\r
+    \r
+        //         this.user.patch(userPatch).subscribe((res) => {\r
+        //             console.log(res)\r
+        //             console.log("Created first default group for user. Default group has been added!")\r
+        //         })\r
+            \r
+        //     }\r
+        //     else {\r
+    \r
+        //         this._groups.setGroup({_id: this.currentUser.defaultGroup})\r
+    \r
+        //     }\r
+        // }\r
+\r
+        //this._groups.setUp();\r
+\r
+        this._groups.listChange().subscribe(res => {\r
+            res = this._groups.format(res);\r
+            //set menu fields\r
+            this.setNavFields(res);\r
+            this.groups = res as Array<NavItem>;\r
+        });\r
+\r
+        this._groups.groupChange().subscribe(res => {\r
+            \r
+            this.selectedGroup = res;\r
+        });\r
+\r
+    }\r
+\r
+    setNavFields(groups){\r
+        groups.forEach((elem, val) => {\r
+            groups[val].displayName = elem.groupName;\r
+            this.setNavFields(groups[val].children);\r
+        });\r
+    }\r
+\r
+    print(){\r
+        \r
+    }\r
+\r
+    changeGroup(group) {\r
+        this.groupMenu.closeMenu();\r
+       // Patch to add update Default Group\r
+\r
+       // If the Default Group Enabled does not exist (users havent saved a default group)\r
+        if (!this.currentUser.defaultGroupEnabled)\r
+        {\r
+            let userPatch = {\r
+                _id : this.currentUser._id,\r
+                defaultGroup: group._id\r
+            };\r
+\r
+            this.user.patch(userPatch).subscribe((res) =>{\r
+                \r
+                \r
+            })\r
+        }\r
+        // If the default Group Enabled exists (Users saved a default group)\r
+        else{\r
+            \r
+            //Take the default group from the user input\r
+        }\r
+\r
+    \r
+        \r
+        this._groups.setGroup(group);\r
+   \r
+    }\r
+\r
+    createGroup(){\r
+        this.modal.open(CreateGroupModalComponent, {\r
+            width: '50%'\r
+        }).afterClosed().subscribe((result) => {\r
+            if(result){\r
+                this.groups.push(result);\r
+            }\r
+        });\r
+    }\r
+    \r
+    isToggled(): boolean {\r
+        const dom: Element = document.querySelector('body');\r
+        return dom.classList.contains(this.pushRightClass);\r
+    }\r
+\r
+    toggleSidebar() {\r
+        const dom: any = document.querySelector('body');\r
+        dom.classList.toggle(this.pushRightClass);\r
+    }\r
+\r
+    rltAndLtr() {\r
+        const dom: any = document.querySelector('body');\r
+        dom.classList.toggle('rtl');\r
+    }\r
+\r
+    onLoggedout() {\r
+        this.auth.logout();\r
+    }\r
+\r
+    changeLang(language: string) {\r
+        this.translate.use(language);\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.html b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.html
new file mode 100644 (file)
index 0000000..a664a3c
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<nav class="sidebar">\r
+    <div class="list-group">\r
+        <a routerLink="/dashboard" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-dashboard"></i>No\r
+        </a>\r
+        <a routerLink="/onboarding" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-user"></i>Yes\r
+        </a>\r
+    </div>\r
+</nav>\r
diff --git a/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.scss b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.scss
new file mode 100644 (file)
index 0000000..66102b3
--- /dev/null
@@ -0,0 +1,176 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+$topnav-background-color: #000;\r
+.sidebar {\r
+    display: none;\r
+    font-size: 1em;\r
+    border-radius: 0;\r
+    position: fixed;\r
+    z-index: 1000;\r
+    top: 56px;\r
+    right: 235px;\r
+    width: 235px;\r
+    margin-right: -235px;\r
+    border: none;\r
+    border-radius: 0;\r
+    overflow-y: auto;\r
+    background-color: $topnav-background-color;\r
+    bottom: 43px;\r
+    overflow-x: hidden;\r
+    padding-bottom: 40px;\r
+    -webkit-transition: all 0.2s ease-in-out;\r
+    -moz-transition: all 0.2s ease-in-out;\r
+    -ms-transition: all 0.2s ease-in-out;\r
+    -o-transition: all 0.2s ease-in-out;\r
+    transition: all 0.2s ease-in-out;\r
+    // border-top: 1px solid rgba(255,255,255,0.3);\r
+    .list-group {\r
+        a.list-group-item {\r
+            background: $topnav-background-color;\r
+            border: 0;\r
+            border-radius: 0;\r
+            color: #999;\r
+            text-decoration: none;\r
+            .fa {\r
+                margin-right: 10px;\r
+            }\r
+        }\r
+        a:hover {\r
+            background: lighten($topnav-background-color, 10%);\r
+            color: #fff;\r
+        }\r
+        a.router-link-active {\r
+            background: lighten($topnav-background-color, 10%);\r
+            color: #fff;\r
+        }\r
+        .header-fields {\r
+            padding-top: 10px;\r
+        \r
+            > .list-group-item:first-child {\r
+                border-top: 1px solid rgba(255, 255, 255, 0.2);\r
+            }\r
+        }\r
+    }\r
+    .sidebar-dropdown {\r
+        *:focus {\r
+            border-radius: none;\r
+            border: none;\r
+        }\r
+        .panel-title {\r
+            font-size: 1rem;\r
+            height: 50px;\r
+            margin-bottom: 0;\r
+            a {\r
+                color: #999;\r
+                text-decoration: none;\r
+                font-weight: 400;\r
+                background: $topnav-background-color;\r
+                span {\r
+                    position: relative;\r
+                    display: block;\r
+                    padding: 0.75rem 1.5rem;\r
+                    padding-top: 1rem;\r
+                }\r
+            }\r
+            a:hover,\r
+            a:focus {\r
+                color: #fff;\r
+                outline: none;\r
+                outline-offset: -2px;\r
+            }\r
+        }\r
+        .panel-title:hover {\r
+            background: lighten($topnav-background-color, 10%);\r
+        }\r
+        .panel-collapse {\r
+            border-radious: 0;\r
+            border: none;\r
+            .panel-body {\r
+                .list-group-item {\r
+                    border-radius: 0;\r
+                    background-color: $topnav-background-color;\r
+                    border: 0 solid transparent;\r
+                    a {\r
+                        color: #999;\r
+                    }\r
+                    a:hover {\r
+                        color: #fff;\r
+                    }\r
+                }\r
+                .list-group-item:hover {\r
+                    background: lighten($topnav-background-color, 10%);\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+.nested-menu {\r
+    .list-group-item {\r
+        cursor: pointer;\r
+    }\r
+    .nested {\r
+        list-style-type: none;\r
+    }\r
+    ul.submenu {\r
+        display: none;\r
+        height: 0;\r
+    }\r
+    & .expand {\r
+        ul.submenu {\r
+            display: block;\r
+            list-style-type: none;\r
+            height: auto;\r
+            li {\r
+                a {\r
+                    color: #fff;\r
+                    padding: 10px;\r
+                    display: block;\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+@media screen and (max-width: 992px) {\r
+    .sidebar {\r
+        top: 54px;\r
+        left: 0px;\r
+    }\r
+}\r
+@media print {\r
+    .sidebar {\r
+        display: none !important;\r
+    }\r
+}\r
+@media (min-width: 992px) {\r
+    .header-fields {\r
+        display: none;\r
+    }\r
+}\r
+\r
+::-webkit-scrollbar {\r
+    width: 8px;\r
+}\r
+\r
+::-webkit-scrollbar-track {\r
+    -webkit-box-shadow: inset 0 0 0px rgba(255, 255, 255, 1);\r
+    border-radius: 3px;\r
+}\r
+\r
+::-webkit-scrollbar-thumb {\r
+    border-radius: 3px;\r
+    -webkit-box-shadow: inset 0 0 3px rgba(255, 255, 255, 1);\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.spec.ts b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.spec.ts
new file mode 100644 (file)
index 0000000..bce5b0d
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { RightSidebarComponent } from './right-sidebar.component';\r
+\r
+describe('RightSidebarComponent', () => {\r
+  let component: RightSidebarComponent;\r
+  let fixture: ComponentFixture<RightSidebarComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ RightSidebarComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(RightSidebarComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.ts b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.ts
new file mode 100644 (file)
index 0000000..cf800fa
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+\r
+@Component({\r
+  selector: 'app-right-sidebar',\r
+  templateUrl: './right-sidebar.component.html',\r
+  styleUrls: ['./right-sidebar.component.scss']\r
+})\r
+export class RightSidebarComponent implements OnInit {\r
+\r
+  constructor() { }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.html b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.html
new file mode 100644 (file)
index 0000000..8f38669
--- /dev/null
@@ -0,0 +1,160 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<nav class="sidebar" [ngClass]="{sidebarPushRight: isActive}">\r
+    <div class="list-group">\r
+        <a routerLink="/dashboard" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-dashboard"></i>&nbsp;{{ 'Dashboard' | translate }}\r
+        </a>\r
+\r
+        <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Test Definitions' | translate }}\r
+        </a> \r
+        <!--<div class="nested-menu">\r
+            <a class="list-group-item" (click)="addExpandClass('definition')">\r
+                <span><i class="fa fa-fw fa-object-group"></i>&nbsp; {{ 'Test Definitions' | translate }}</span>\r
+            </a>\r
+            <li class="nested" [class.expand]="showMenu === 'definition'">\r
+                <ul class="submenu">\r
+                    <li>\r
+                        <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-list-ul"></i>&nbsp;{{ 'Collection' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <li>\r
+                        <a [routerLink]="['/modeler']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa bpmn-icon-bpmn-io"></i>&nbsp;{{ 'BPMN Modeler' | translate }}\r
+                        </a>\r
+                    </li>\r
+                </ul>\r
+        </div> -->\r
+        <a [routerLink]="['/test-instances']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-clone"></i>&nbsp;{{ 'Test Instances' | translate }}\r
+        </a>\r
+        <!--<a [routerLink]="['/test-executions']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-bolt"></i>&nbsp;{{ 'Test Executions' | translate }}\r
+        </a>\r
+        <a routerLink="/onboarding" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-user"></i>&nbsp;{{ 'Onboarding' | translate }}\r
+        </a>-->\r
+        <!--<div class="nested-menu">\r
+            <a class="list-group-item" (click)="addExpandClass('pages1')">\r
+                <span><i class="fa fa-fw fa-user"></i>&nbsp;{{ 'Onboarding' | translate }}</span>\r
+            </a>\r
+            <li class="nested" [class.expand]="showMenu === 'pages1'">\r
+                <ul class="submenu">\r
+                    <li>\r
+                        <a [routerLink]="['/onboarding/test-head']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-gears"></i>&nbsp;{{ 'Onboard Test Head' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <li>\r
+                        <a [routerLink]="['/onboarding/test-definition']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Onboard Test Definition' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <li>\r
+                        <a [routerLink]="['/onboarding/test-instances']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-clone"></i>&nbsp;{{ 'Create Test Instance' | translate }}\r
+                        </a>\r
+                    </li>\r
+                </ul>\r
+            </li>\r
+        </div> -->\r
+        <div class="nested-menu">\r
+            <a class="list-group-item" (click)="addExpandClass('pages2')">\r
+                <span><i class="fa fa-folder"></i>&nbsp; {{ 'Resources' | translate }}</span>\r
+            </a>\r
+            <li class="nested" [class.expand]="showMenu === 'pages2'">\r
+                <ul class="submenu">\r
+                    <li>\r
+                        <a [routerLink]="['/test-heads']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-gears"></i>&nbsp;{{ 'Virtual Test Heads' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <!--<li>\r
+                        <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Test Definitions' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <li>\r
+                        <a [routerLink]="['/test-instances']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-clone"></i>&nbsp;{{ 'Test Instances' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <li>\r
+                        <a [routerLink]="['/test-executions']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-bolt"></i>&nbsp;{{ 'Test Executions' | translate }}\r
+                        </a>\r
+                    </li>-->\r
+                </ul>\r
+        </div>\r
+        <!--<a routerLink="/scheduling" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-calendar"></i>&nbsp;{{ 'Scheduling' | translate }}\r
+        </a>-->\r
+        <a [routerLink]="['/modeler']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa bpmn-icon-bpmn-io"></i>&nbsp; Test Designer <small style="color: green">beta</small>\r
+        </a>\r
+        <a *ngIf="canManageGroup" [routerLink]="['/manage-group']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-group"></i>&nbsp;{{ 'Manage Group' | translate }}\r
+        </a>\r
+        <a [routerLink]="['/feedback']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+            <i class="fa fa-fw fa-comment-o"></i>&nbsp;{{ 'Feedback' | translate }}\r
+        </a>\r
+     \r
+\r
+        <div *ngIf="checkIsAdmin()" class="nested-menu">\r
+            <a class="list-group-item" (click)="addExpandClass('admin')">\r
+                <span><i class="fa fa-fw fa-shield"></i>&nbsp; {{ 'Admin' | translate }}</span>\r
+            </a>\r
+            <li class="nested" [class.expand]="showMenu === 'admin'">\r
+                <ul class="submenu">\r
+                    <li>\r
+                        <a [routerLink]="['/user-management']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-user"></i>&nbsp;{{ 'User Management' | translate }}\r
+                        </a>\r
+                    </li>\r
+                </ul>\r
+        </div>\r
+        <!--<div class="nested-menu">\r
+            <a class="list-group-item" (click)="addExpandClass('pages3')">\r
+                <span><i class="fa fa-fw fa-users"></i>&nbsp; {{ 'Manage Group' | translate }}</span>\r
+            </a>\r
+            <li class="nested" [class.expand]="showMenu === 'pages3'">\r
+                <ul class="submenu">\r
+                    <li>\r
+                        <a [routerLink]="['/manageGroupUsers']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-user"></i>&nbsp;{{ 'User Management' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    <li>\r
+                        <a [routerLink]="['/manageGroup']" [routerLinkActive]="['router-link-active']" class="list-group-item">\r
+                            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Manage Group' | translate }}\r
+                        </a>\r
+                    </li>\r
+                    \r
+                </ul>\r
+        </div> -->\r
+\r
+        <a style="position:absolute; bottom: 80px; width: 100%" class="list-group-item" (click)="setHealthStatus()">\r
+            TCU Engine <small *ngIf="tcuengine" style="color:green">Running</small><small *ngIf="!tcuengine" style="color: red">Down</small>\r
+        </a>\r
+        <a style="position:absolute; bottom: 40px; width: 100%" class="list-group-item" (click)="setHealthStatus()">\r
+            TCU API <small *ngIf="tcuapi" style="color:green">Running</small><small *ngIf="!tcuapi" style="color: red">Down</small>\r
+        </a>\r
+        <a style="position:absolute; bottom: 0px; width:100%" class="list-group-item"> {{version}} </a>\r
+    </div>\r
+</nav>\r
diff --git a/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.scss b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.scss
new file mode 100644 (file)
index 0000000..427e2f4
--- /dev/null
@@ -0,0 +1,175 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+$topnav-background-color: #000;\r
+.sidebar {\r
+    font-size: 1em;\r
+    border-radius: 0;\r
+    position: fixed;\r
+    z-index: 1000;\r
+    top: 56px;\r
+    left: 235px;\r
+    width: 235px;\r
+    margin-left: -235px;\r
+    border: none;\r
+    border-radius: 0;\r
+    overflow-y: auto;\r
+    background-color: $topnav-background-color;\r
+    bottom: 0;\r
+    overflow-x: hidden;\r
+    padding-bottom: 40px;\r
+    -webkit-transition: all 0.2s ease-in-out;\r
+    -moz-transition: all 0.2s ease-in-out;\r
+    -ms-transition: all 0.2s ease-in-out;\r
+    -o-transition: all 0.2s ease-in-out;\r
+    transition: all 0.2s ease-in-out;\r
+    // border-top: 1px solid rgba(255,255,255,0.3);\r
+    .list-group {\r
+        a.list-group-item {\r
+            background: $topnav-background-color;\r
+            border: 0;\r
+            border-radius: 0;\r
+            color: #999;\r
+            text-decoration: none;\r
+            .fa {\r
+                margin-right: 10px;\r
+            }\r
+        }\r
+        a:hover {\r
+            background: lighten($topnav-background-color, 10%);\r
+            color: #fff;\r
+        }\r
+        a.router-link-active {\r
+            background: lighten($topnav-background-color, 10%);\r
+            color: #fff;\r
+        }\r
+        .header-fields {\r
+            padding-top: 10px;\r
+        \r
+            > .list-group-item:first-child {\r
+                border-top: 1px solid rgba(255, 255, 255, 0.2);\r
+            }\r
+        }\r
+    }\r
+    .sidebar-dropdown {\r
+        *:focus {\r
+            border-radius: none;\r
+            border: none;\r
+        }\r
+        .panel-title {\r
+            font-size: 1rem;\r
+            height: 50px;\r
+            margin-bottom: 0;\r
+            a {\r
+                color: #999;\r
+                text-decoration: none;\r
+                font-weight: 400;\r
+                background: $topnav-background-color;\r
+                span {\r
+                    position: relative;\r
+                    display: block;\r
+                    padding: 0.75rem 1.5rem;\r
+                    padding-top: 1rem;\r
+                }\r
+            }\r
+            a:hover,\r
+            a:focus {\r
+                color: #fff;\r
+                outline: none;\r
+                outline-offset: -2px;\r
+            }\r
+        }\r
+        .panel-title:hover {\r
+            background: lighten($topnav-background-color, 10%);\r
+        }\r
+        .panel-collapse {\r
+            border-radious: 0;\r
+            border: none;\r
+            .panel-body {\r
+                .list-group-item {\r
+                    border-radius: 0;\r
+                    background-color: $topnav-background-color;\r
+                    border: 0 solid transparent;\r
+                    a {\r
+                        color: #999;\r
+                    }\r
+                    a:hover {\r
+                        color: #fff;\r
+                    }\r
+                }\r
+                .list-group-item:hover {\r
+                    background: lighten($topnav-background-color, 10%);\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+.nested-menu {\r
+    .list-group-item {\r
+        cursor: pointer;\r
+    }\r
+    .nested {\r
+        list-style-type: none;\r
+    }\r
+    ul.submenu {\r
+        display: none;\r
+        height: 0;\r
+    }\r
+    & .expand {\r
+        ul.submenu {\r
+            display: block;\r
+            list-style-type: none;\r
+            height: auto;\r
+            li {\r
+                a {\r
+                    color: #fff;\r
+                    padding: 10px;\r
+                    display: block;\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+@media screen and (max-width: 992px) {\r
+    .sidebar {\r
+        top: 54px;\r
+        left: 0px;\r
+    }\r
+}\r
+@media print {\r
+    .sidebar {\r
+        display: none !important;\r
+    }\r
+}\r
+@media (min-width: 992px) {\r
+    .header-fields {\r
+        display: none;\r
+    }\r
+}\r
+\r
+::-webkit-scrollbar {\r
+    width: 8px;\r
+}\r
+\r
+::-webkit-scrollbar-track {\r
+    -webkit-box-shadow: inset 0 0 0px rgba(255, 255, 255, 1);\r
+    border-radius: 3px;\r
+}\r
+\r
+::-webkit-scrollbar-thumb {\r
+    border-radius: 3px;\r
+    -webkit-box-shadow: inset 0 0 3px rgba(255, 255, 255, 1);\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.spec.ts b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.spec.ts
new file mode 100644 (file)
index 0000000..994bb5e
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'\r
+import { RouterTestingModule } from '@angular/router/testing'\r
+import { TranslateModule } from '@ngx-translate/core'\r
+\r
+import { SidebarComponent } from './sidebar.component'\r
+import { LayoutModule } from '../../layout.module'\r
+\r
+describe('SidebarComponent', () => {\r
+  let component: SidebarComponent\r
+  let fixture: ComponentFixture<SidebarComponent>\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      imports: [\r
+        LayoutModule,\r
+        RouterTestingModule,\r
+        TranslateModule.forRoot(),\r
+      ],\r
+    })\r
+    .compileComponents()\r
+  }))\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(SidebarComponent)\r
+    component = fixture.componentInstance\r
+    fixture.detectChanges()\r
+  })\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy()\r
+  })\r
+})\r
diff --git a/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.ts b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.ts
new file mode 100644 (file)
index 0000000..8034055
--- /dev/null
@@ -0,0 +1,147 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { Router, NavigationEnd } from '@angular/router';\r
+import { TranslateService } from '@ngx-translate/core';\r
+import { AppGlobals } from 'app/app.global';\r
+import {CookieService} from "ngx-cookie-service";\r
+import { HealthService } from 'app/shared/services/health.service';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { Group, Groups } from 'app/shared/models/group.model';\r
+\r
+@Component({\r
+    selector: 'app-sidebar',\r
+    templateUrl: './sidebar.component.html',\r
+    styleUrls: ['./sidebar.component.scss']\r
+})\r
+export class SidebarComponent implements OnInit {\r
+    isActive: boolean = false;\r
+    showMenu: string = '';\r
+    pushRightClass: string = 'push-right';\r
+    version = AppGlobals.version\r
+    tcuapi: boolean;\r
+    tcuengine: boolean;\r
+    isAdmin: boolean = false;\r
+\r
+    canManageGroup = false;\r
+\r
+    currentGroupId;\r
+\r
+    constructor(private translate: TranslateService, public router: Router, public user: UserService, private health: HealthService, private cookie: CookieService, public group: GroupService) {\r
+        this.translate.addLangs(['en', 'fr', 'ur', 'es', 'it', 'fa', 'de']);\r
+        this.translate.setDefaultLang('en');\r
+        const browserLang = this.translate.getBrowserLang();\r
+        this.translate.use(browserLang.match(/en|fr|ur|es|it|fa|de/) ? browserLang : 'en');\r
+        this.checkIsAdmin();\r
+        this.router.events.subscribe(val => {\r
+            if (\r
+                val instanceof NavigationEnd &&\r
+                window.innerWidth <= 992 &&\r
+                this.isToggled()\r
+            ) {\r
+                this.toggleSidebar();\r
+            }\r
+        });\r
+    }\r
+\r
+    ngOnInit(){\r
+        if(this.group.getGroup()){\r
+            this.checkManage(this.group.getGroup());\r
+        }\r
+        this.group.groupChange().subscribe(group => {\r
+            this.checkManage(group);\r
+        })\r
+        this.setHealthStatus();\r
+    }\r
+\r
+    checkManage(group){\r
+        this.canManageGroup = this.user.ability.can('management', new Groups(group));\r
+    }\r
+\r
+    setHealthStatus(){\r
+        this.health.get('tcu-api').subscribe(res => {\r
+            if(res['code'] == 200 || res['statusCode'] == 200){\r
+                this.tcuapi = true;\r
+            }else{\r
+                this.tcuapi = false;\r
+            }\r
+        }, err => {\r
+            this.tcuapi = false;\r
+        });\r
+\r
+        this.health.get('tcu-engine').subscribe(res => {\r
+            \r
+            if(res['code'] == 200 || res['statusCode'] == 200){\r
+                this.tcuengine = true;\r
+            }else{\r
+                this.tcuengine = false;\r
+            }\r
+        }, err => {\r
+            \r
+            this.tcuengine = false;\r
+        });\r
+    }\r
+\r
+    eventCalled() {\r
+        this.isActive = !this.isActive;\r
+    }\r
+\r
+    addExpandClass(element: any) {\r
+        if (element === this.showMenu) {\r
+            this.showMenu = '0';\r
+        } else {\r
+            this.showMenu = element;\r
+        }\r
+    }\r
+\r
+    isToggled(): boolean {\r
+        const dom: Element = document.querySelector('body');\r
+        return dom.classList.contains(this.pushRightClass);\r
+    }\r
+\r
+    toggleSidebar() {\r
+        const dom: any = document.querySelector('body');\r
+        dom.classList.toggle(this.pushRightClass);\r
+    }\r
+\r
+    rltAndLtr() {\r
+        const dom: any = document.querySelector('body');\r
+        dom.classList.toggle('rtl');\r
+    }\r
+\r
+    changeLang(language: string) {\r
+        this.translate.use(language);\r
+    }\r
+\r
+    onLoggedout() {\r
+        localStorage.removeItem('isLoggedin');\r
+    }\r
+\r
+    checkIsAdmin() {\r
+        if (this.cookie.get('access_token') && this.cookie.get('currentUser')) {\r
+            let currentUser = JSON.parse(this.cookie.get('currentUser'));\r
+            if (currentUser['permissions'].indexOf('admin') >= 0) {\r
+                this.isAdmin = true;\r
+                return true;\r
+            }\r
+        }\r
+        this.isAdmin = false;\r
+        return false;\r
+    }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot(2).png b/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot(2).png
new file mode 100644 (file)
index 0000000..6fc07ef
Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot(2).png differ
diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif b/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif
new file mode 100644 (file)
index 0000000..2fad5ae
Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif differ
diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif b/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif
new file mode 100644 (file)
index 0000000..22ccbe3
Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif differ
diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif
new file mode 100644 (file)
index 0000000..5d979f7
Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif differ
diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg
new file mode 100644 (file)
index 0000000..d34f7b3
--- /dev/null
@@ -0,0 +1 @@
+<svg width="200px"  height="200px"  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-wedges" style="background: none;"><g transform="translate(50,50)"><g ng-attr-transform="scale({{config.scale}})" transform="scale(0.7)"><g transform="translate(-50,-50)"><g transform="rotate(327.872 50.0001 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="0.75s" begin="0s" repeatCount="indefinite"></animateTransform><path ng-attr-fill-opacity="{{config.opacity}}" ng-attr-fill="{{config.c1}}" d="M50 50L50 0A50 50 0 0 1 100 50Z" fill-opacity="0.8" fill="#1d3f72"></path></g><g transform="rotate(245.904 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform><path ng-attr-fill-opacity="{{config.opacity}}" ng-attr-fill="{{config.c2}}" d="M50 50L50 0A50 50 0 0 1 100 50Z" transform="rotate(90 50 50)" fill-opacity="0.8" fill="#5699d2"></path></g><g transform="rotate(163.936 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1.5s" begin="0s" repeatCount="indefinite"></animateTransform><path ng-attr-fill-opacity="{{config.opacity}}" ng-attr-fill="{{config.c3}}" d="M50 50L50 0A50 50 0 0 1 100 50Z" transform="rotate(180 50 50)" fill-opacity="0.8" fill="#d8ebf9"></path></g><g transform="rotate(81.9679 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="3s" begin="0s" repeatCount="indefinite"></animateTransform><path ng-attr-fill-opacity="{{config.opacity}}" ng-attr-fill="{{config.c4}}" d="M50 50L50 0A50 50 0 0 1 100 50Z" transform="rotate(270 50 50)" fill-opacity="0.8" fill="#71c2cc"></path></g></g></g></g></svg>
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.pug b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.pug
new file mode 100644 (file)
index 0000000..d895b76
--- /dev/null
@@ -0,0 +1,139 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(style="position: relative;", ng-model)\r
+\r
+  .row()\r
+\r
+    h4(mat-dialog-title, style="padding-left: 15px;") Statistics Filters\r
+\r
+    button(mat-icon-button, (click)="close()", style="position: absolute; right: 0px;")\r
+      mat-icon close\r
+\r
+  div(mat-dialog-content)\r
+\r
+    //- mat-expansion-panel(style="margin-top: 5px;")\r
+    //-   mat-expansion-panel-header \r
+    //-     mat-panel-title(style="font-weight: bold") Overall\r
+    //-     //- mat-panel-description Filters for all charts. \r
+    //-   .row\r
+    //-     .col-3\r
+    //-       mat-form-field\r
+    //-         input(matInput, [matDatepicker]="allStartPicker", [(ngModel)]="allFilters.startDate", [min]="minDate", [max]="maxDate", placeholder="Start Date")\r
+    //-         mat-datepicker-toggle(matSuffix [for]="allStartPicker")\r
+    //-         mat-datepicker(#allStartPicker)\r
+\r
+    //-       mat-form-field\r
+    //-         input(matInput, [matDatepicker]="allEndPicker", [(ngModel)]="allFilters.endDate", [min]="minDate", [max]="maxDate", placeholder="End Date")\r
+    //-         mat-datepicker-toggle(matSuffix [for]="allEndPicker")\r
+    //-         mat-datepicker(#allEndPicker)\r
+\r
+    mat-expansion-panel\r
+      mat-expansion-panel-header \r
+        mat-panel-title(style="font-weight: bold") Test Definitions\r
+        //- mat-panel-description Filters for test definition charts.\r
+      .row\r
+        .col-6\r
+\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              mat-label Test Definitions\r
+              mat-select( [(value)]="tdFilters.selected", multiple)\r
+                mat-option(*ngFor="let testDefinition of testDefinitions", [value]="testDefinition") {{testDefinition.viewValue}}\r
+\r
+        .col-6\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              input(matInput, [matDatepicker]="TDStartPicker", [(ngModel)]="tdFilters.startDate", [min]="minDate", [max]="maxDate", placeholder="Start Date")\r
+              mat-datepicker-toggle(matSuffix [for]="TDStartPicker")\r
+              mat-datepicker(#TDStartPicker)\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              input(matInput, [matDatepicker]="TDEndPicker", [(ngModel)]="tdFilters.endDate", [min]="minDate", [max]="maxDate", placeholder="End Date")\r
+              mat-datepicker-toggle(matSuffix [for]="TDEndPicker")\r
+              mat-datepicker(#TDEndPicker) \r
+\r
+    mat-expansion-panel\r
+      mat-expansion-panel-header \r
+        mat-panel-title(style="font-weight: bold") Test Instances\r
+        //- mat-panel-description Filters for test instance charts.\r
+      .row\r
+\r
+        .col-6\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              mat-label Test Definitions\r
+              mat-select([(value)]="tiFilters.selectedTDs", multiple)\r
+                mat-option(*ngFor="let testDefinition of testDefinitions", [value]="testDefinition.id") {{testDefinition.viewValue}}\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              mat-label Test Instances\r
+              mat-select([(value)]="tiFilters.selectedTIs", multiple)\r
+                mat-option(*ngFor="let testInstance of testInstances", [value]="testInstance.id") {{testInstance.viewValue}}\r
+\r
+        .col-6\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              input(matInput, [matDatepicker]="TIStartPicker", [(ngModel)]="tiFilters.startDate", [min]="minDate", [max]="maxDate", placeholder="Start Date")\r
+              mat-datepicker-toggle(matSuffix [for]="TIStartPicker")\r
+              mat-datepicker(#TIStartPicker)\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              input(matInput, [matDatepicker]="TIEndPicker", [(ngModel)]="tiFilters.endDate", [min]="minDate", [max]="maxDate", placeholder="End Date")\r
+              mat-datepicker-toggle(matSuffix [for]="TIEndPicker")\r
+              mat-datepicker(#TIEndPicker) \r
+\r
+    mat-expansion-panel(style="margin-bottom: 5px;")\r
+      mat-expansion-panel-header \r
+        mat-panel-title(style="font-weight: bold") Scheduled Tests\r
+        //- mat-panel-description Filters for test schedule table.\r
+\r
+      .row   \r
+\r
+        .col-6\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              mat-label Test Instances\r
+              mat-select([(value)]="scheduleFilters.selectedInstances", multiple)\r
+                mat-option(*ngFor="let instance of testInstances", [value]="instance.id") {{instance.viewValue}}\r
+\r
+        .col-6\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              input(matInput, [matDatepicker]="scheduleStartPicker", [(ngModel)]="scheduleFilters.startDate", placeholder="Start Date")\r
+              mat-datepicker-toggle(matSuffix [for]="scheduleStartPicker")\r
+              mat-datepicker(#scheduleStartPicker)\r
+          .row\r
+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+              input(matInput, [matDatepicker]="scheduleEndPicker", [(ngModel)]="scheduleFilters.endDate", placeholder="End Date")\r
+              mat-datepicker-toggle(matSuffix [for]="scheduleEndPicker")\r
+            mat-datepicker(#scheduleEndPicker)\r
+\r
+        //- .col-3\r
+        //-   .row\r
+        //-     mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+        //-       input(matInput, [matDatepicker]="scheduleStartTimePicker", [(ngModel)]="scheduleFilters.timeRangeStart", [min]="minDate", [max]="maxDate", placeholder="Time Range Start")\r
+        //-       mat-datepicker-toggle(matSuffix [for]="scheduleStartTimePicker")\r
+        //-       mat-datepicker(#scheduleStartTimePicker)\r
+        //-   .row\r
+        //-     mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")\r
+        //-       input(matInput, [matDatepicker]="scheduleEndTimePicker", [(ngModel)]="scheduleFilters.timeRangeEnd", [min]="minDate", [max]="maxDate", placeholder="Time Range End")\r
+        //-       mat-datepicker-toggle(matSuffix [for]="scheduleEndTimePicker")\r
+        //-     mat-datepicker(#scheduleEndTimePicker)\r
+\r
+    .row(style="padding: 10px;")\r
+      //- button(mat-raised-button, style="margin-left: auto; margin-right: 5px;") Clear All\r
+      button(mat-raised-button, color="primary", style="margin-right: auto; margin-left: auto;", (click)="onConfirm()") Set
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.scss b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.scss
new file mode 100644 (file)
index 0000000..20ad5c4
--- /dev/null
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+*{\r
+    //border: 1px solid black;\r
+}\r
+\r
+hr{\r
+    padding: 0px;\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..c04bb71
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { FilterModalComponent } from './filter-modal.component';\r
+\r
+describe('FilterModalComponent', () => {\r
+  let component: FilterModalComponent;\r
+  let fixture: ComponentFixture<FilterModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ FilterModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(FilterModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.ts b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.ts
new file mode 100644 (file)
index 0000000..c1ab2c7
--- /dev/null
@@ -0,0 +1,151 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';\r
+import { FormControl } from '@angular/forms';\r
+import * as moment from 'moment';\r
+\r
+import { StatsService } from '../stats.service';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { TestInstanceService } from 'app/shared/services/test-instance.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+\r
+@Component({\r
+  selector: 'app-filter-modal',\r
+  templateUrl: './filter-modal.component.pug',\r
+  styleUrls: ['./filter-modal.component.scss']\r
+})\r
+\r
+export class FilterModalComponent implements OnInit {\r
+\r
+  public group;\r
+  public allFilters = {\r
+    startDate: "",\r
+    endDate: ""\r
+  };\r
+  public tdFilters = {\r
+    selected: [],\r
+    startDate: "",\r
+    endDate: "",\r
+  };\r
+  public tiFilters = {\r
+    selectedTDs: [],\r
+    selectedTIs: [],\r
+    startDate: "",\r
+    endDate: "",\r
+    multiLineLimit: 5\r
+  };\r
+  public scheduleFilters = {\r
+    startDate: "",\r
+    endDate: "",\r
+    timeRangeStart: "",\r
+    timeRangeEnd: "",\r
+    selectedInstances: [],\r
+  }\r
+  // public vthFilters = {\r
+  //   selected: [],\r
+  //   startDate: "",\r
+  //   endDate: "",\r
+  // };\r
+\r
+  public minDate;\r
+  public maxDate;\r
+\r
+  public testDefinitions: Array<any> = [];\r
+  public testInstances: Array<any> = [];\r
+  //public scheduleInstances: Array<any> = [];\r
+  //public vths = [];\r
+\r
+  constructor(\r
+    public dialogRef: MatDialogRef<FilterModalComponent>,\r
+    public statsService: StatsService,\r
+    public tdService: TestDefinitionService,\r
+    public groupService: GroupService,\r
+    public tiService: TestInstanceService\r
+  ) {\r
+    this.minDate = new Date(moment().subtract(1, 'year').format('L'));\r
+    this.maxDate = new Date(moment().format('L'));\r
+\r
+  }\r
+\r
+  ngOnInit() {\r
+    //populate the td, ti, and vth arrays up there. or import them?\r
+    this.setTDList();\r
+    this.setTIList();\r
+  }\r
+\r
+  setTDList() {\r
+    this.tdService.find({\r
+      groupId: this.groupService.getGroup()["_id"],\r
+      $select: ['testName', 'testDescription', "_id"],\r
+      $limit: -1,\r
+      $sort:{\r
+        testName:1\r
+      }\r
+    }).subscribe(result => {\r
+      for (let index in result) {\r
+        this.testDefinitions.push({id: result[index]._id, viewValue: result[index].testName });\r
+      }\r
+    })\r
+  }\r
+\r
+  setTIList() {\r
+    this.tiService.find({\r
+      groupId: this.groupService.getGroup()["_id"],\r
+      $select: ['testInstanceName', 'testInstanceDescription', "_id"],\r
+      $limit: -1,\r
+      $sort:{\r
+        testInstanceName:1\r
+      }\r
+    }).subscribe(result => {\r
+      //console.log(result);\r
+      for (let index in result) {\r
+        this.testInstances.push({ id: result[index]._id, viewValue: result[index].testInstanceName })\r
+      }\r
+      //this.testInstances.sort((a, b) => b.viewValue - a.viewValue);\r
+    })\r
+  }\r
+\r
+  checkDates() {\r
+    let allSet = true;\r
+\r
+    if (this.scheduleFilters.startDate > this.scheduleFilters.endDate) {\r
+      allSet = false;\r
+      alert("Schedule Filters: Your end date cannot be earlier than your start date.");\r
+    } else if (this.tdFilters.startDate > this.tdFilters.endDate) {\r
+      allSet = false;\r
+      alert("Test Definition Filters: Your end date cannot be earlier than your start date.");\r
+    } else if (this.tiFilters.startDate > this.tiFilters.endDate) {\r
+      allSet = false;\r
+      alert("Test Instance Filters: Your end date cannot be earlier than your start date.");\r
+    }\r
+    return allSet;\r
+  }\r
+\r
+  onConfirm() {\r
+    if (this.checkDates() == true) {\r
+      this.close();\r
+      this.statsService.filterData(this.allFilters, this.tdFilters, this.tiFilters, this.scheduleFilters);\r
+      //console.log(this.tdFilters);\r
+    }\r
+  }\r
+\r
+  close() {\r
+    this.dialogRef.close();\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.pug
new file mode 100644 (file)
index 0000000..d2f9bbd
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#horizBarChartDiv, [style.height]="height")\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..79d017d
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { HorizBarChartComponent } from './horiz-bar-chart.component';\r
+\r
+describe('HorizBarChartComponent', () => {\r
+  let component: HorizBarChartComponent;\r
+  let fixture: ComponentFixture<HorizBarChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ HorizBarChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(HorizBarChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.ts
new file mode 100644 (file)
index 0000000..af6175e
--- /dev/null
@@ -0,0 +1,175 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, NgZone, Input, ViewChild, ElementRef, OnDestroy, } from '@angular/core';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import { StatsService } from '../stats.service'\r
+\r
+// am4core.useTheme(am4themes_animated);\r
+\r
+@Component({\r
+  selector: 'app-horiz-bar-chart',\r
+  templateUrl: './horiz-bar-chart.component.pug',\r
+  styleUrls: ['./horiz-bar-chart.component.scss']\r
+})\r
+export class HorizBarChartComponent implements OnInit, OnDestroy {\r
+\r
+  @ViewChild('horizBarChartDiv') HorizBarChartDiv: ElementRef;\r
+  @Input() height: string;\r
+\r
+  public chart: am4charts.XYChart;\r
+  public testInstanceData;\r
+  public loadingIndicator;\r
+  protected stats: StatsService;\r
+\r
+  constructor(private statsService: StatsService) {\r
+    this.stats = statsService;\r
+  }\r
+\r
+\r
+  ngOnInit() {\r
+    this.renderChart();\r
+\r
+    this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    })\r
+\r
+    this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData()\r
+    })\r
+\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.chart.dispose();\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+    if(!this.loadingIndicator){\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight= "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    }else{\r
+      this.loadingIndicator.show();\r
+    }\r
+\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  setChartData() {\r
+    this.testInstanceData = this.stats.getData('testInstances');\r
+    this.chart.data = this.testInstanceData;\r
+\r
+    // Displays the average time for each bar. \r
+    // If there is no time recorded for the Test Instance, display No Time Recorded.\r
+    let series = this.chart.series.values[0] as am4charts.ColumnSeries;\r
+    \r
+    \r
+    series.columns.template.adapter.add("tooltipText", (text, target) => {\r
+      if (target.dataItem) {\r
+        if (this.chart.data[target.dataItem.index].Average > 0) {\r
+          return this.chart.data[target.dataItem.index].Count.toString() + " Executions \n Avg Time: " + this.chart.data[target.dataItem.index].Average.toFixed(2).toString() + " seconds";\r
+        } else\r
+          return this.chart.data[target.dataItem.index].Count.toString() + " Executions \n No Time Recorded";\r
+      }\r
+    });\r
+    series.columns.template.adapter.add("fill", (fill, target) => this.chart.colors.getIndex(target.dataItem.index));\r
+    // console.log(this.chart.yAxes);\r
+    this.chart.yAxes.values[0].zoomToIndexes(0, 6, false, true);\r
+    this.hideLoadingIndicator();\r
+    \r
+  }\r
+\r
+  renderChart() {\r
+    this.chart = am4core.create(this.HorizBarChartDiv.nativeElement, am4charts.XYChart);\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+    this.showLoadingIndicator();\r
+\r
+    this.chart.responsive.enabled = true;\r
+\r
+    // Create axes\r
+    var categoryAxis = this.chart.yAxes.push(new am4charts.CategoryAxis());\r
+    categoryAxis.dataFields.category = "Name";\r
+    categoryAxis.numberFormatter.numberFormat = "#";\r
+    categoryAxis.renderer.inversed = true;\r
+    categoryAxis.renderer.minGridDistance = 5;\r
+    categoryAxis.title.text = "Test Instances";\r
+    categoryAxis.title.fontSize = "10px";\r
+\r
+    var valueAxis = this.chart.xAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.renderer.minWidth = 10;\r
+\r
+    // Create series\r
+    var series = this.chart.series.push(new am4charts.ColumnSeries());\r
+    series.dataFields.valueX = "Count";\r
+    series.dataFields.categoryY = "Name";\r
+    series.columns.template.tooltipText = " ";\r
+\r
+    let label = categoryAxis.renderer.labels.template;\r
+    label.truncate = true;\r
+    label.maxWidth = 130;\r
+    label.fontSize = 13;\r
+\r
+    //Scrollbar on the right. \r
+    let scrollBarY = new am4charts.XYChartScrollbar();\r
+    scrollBarY.series.push(series);\r
+    this.chart.scrollbarY = scrollBarY;\r
+    this.chart.scrollbarY.contentHeight = 100;\r
+    this.chart.scrollbarY.minWidth = 20;\r
+    this.chart.scrollbarY.thumb.minWidth = 20;\r
+\r
+    //set initial Scrollbar Zoom to the Top 6 Instances. \r
+  //   this.chart.events.on("ready", () => {\r
+  //     console.log("here")\r
+  //     categoryAxis.zoomToIndexes(0, 6, false, true);\r
+  //   });\r
+   }\r
+\r
+}\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.pug
new file mode 100644 (file)
index 0000000..e26fb12
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#linechartdiv, [style.height]="height")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..4201928
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { LineChartComponent } from './line-chart.component';\r
+\r
+describe('LineChartComponent', () => {\r
+  let component: LineChartComponent;\r
+  let fixture: ComponentFixture<LineChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ LineChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(LineChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.ts
new file mode 100644 (file)
index 0000000..e7f0780
--- /dev/null
@@ -0,0 +1,172 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, ElementRef, OnDestroy } from '@angular/core';\r
+import { StatsService } from '../stats.service';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import { _ } from 'ag-grid-community';\r
+import * as moment from 'moment';\r
+import { Subscription } from 'rxjs';\r
+\r
+// am4core.useTheme(am4themes_animated);\r
+\r
+@Component({\r
+  selector: 'app-line-chart',\r
+  templateUrl: './line-chart.component.pug',\r
+  styleUrls: ['./line-chart.component.scss']\r
+})\r
+export class LineChartComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  @ViewChild('linechartdiv') LineChartDiv: ElementRef;\r
+  @Input() height: string;\r
+\r
+  //public testDefinitionName = "Hello";\r
+  private chart: am4charts.XYChart;\r
+  private loadingIndicator;\r
+\r
+  constructor(private stats: StatsService) {\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.renderChart();\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+  }\r
+\r
+  ngOnDestroy(){\r
+    //destory chart\r
+    this.chart.dispose();\r
+  }\r
+\r
+  //Sets count to 0 for any dates that dont have data\r
+  setupPoints(rawData) {\r
+    \r
+    let formattedData = []; \r
+    let dayRange = moment(this.stats.filters.endDate).add(1, 'days').diff(moment(this.stats.filters.startDate), 'days');\r
+    \r
+    for(let i = 0; i < dayRange; i++){\r
+      //find date in raw data\r
+      let d = rawData.find(e => moment(e.date).isSame(moment(this.stats.filters.startDate).add(i, 'days'), 'day'));\r
+      let count = 0;\r
+      if(d){\r
+        count = d.count;\r
+      }\r
+      formattedData.push({\r
+        date: moment(this.stats.filters.startDate).startOf('day').add(i, 'days').toDate(),\r
+        count: count\r
+      })\r
+    }\r
+\r
+    return formattedData;\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+\r
+    //this.height = "380px";\r
+    if(!this.loadingIndicator){\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight = "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    }else{\r
+      this.loadingIndicator.show();\r
+    }\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  setChartData() {\r
+    let executions = this.stats.getData('TD_Executions');\r
+    this.chart.data = this.setupPoints(executions);\r
+\r
+    this.hideLoadingIndicator();\r
+  }\r
+\r
+  renderChart() {\r
+\r
+    if(this.chart){\r
+      this.chart.dispose();\r
+    }\r
+    this.chart = am4core.create(this.LineChartDiv.nativeElement, am4charts.XYChart);\r
+    this.chart.preloader.disabled = true;\r
+    this.showLoadingIndicator();\r
+\r
+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
+    dateAxis.fontSize = "10px";\r
+\r
+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.title.text = "Executions";\r
+    valueAxis.title.fontSize = "10px";\r
+\r
+    let series = this.chart.series.push(new am4charts.LineSeries());\r
+    series.dataFields.dateX = "date";\r
+    series.dataFields.valueY = "count";\r
+    series.strokeWidth = 3;\r
+\r
+    series.fillOpacity = .5;\r
+    // series.tensionX = 0.8;\r
+    series.sequencedInterpolation = false;\r
+    series.tooltipText = "{valueY.value}";\r
+\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+\r
+    this.chart.responsive.enabled = true;\r
+  }\r
+\r
+\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.pug
new file mode 100644 (file)
index 0000000..940e640
--- /dev/null
@@ -0,0 +1,19 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+p(style="width: 100%; text-align: center;") Test Instance Executions over time\r
+hr\r
+div(#multilinechartdiv, [style.height]="height")\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..e43604d
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { MultiLineChartComponent } from './multi-line-chart.component';\r
+\r
+describe('MultiLineChartComponent', () => {\r
+  let component: MultiLineChartComponent;\r
+  let fixture: ComponentFixture<MultiLineChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ MultiLineChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(MultiLineChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.ts
new file mode 100644 (file)
index 0000000..e9782c4
--- /dev/null
@@ -0,0 +1,336 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';\r
+import { StatsService } from '../stats.service';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import * as moment from 'moment';\r
+\r
+//am4core.useTheme(am4themes_animated);\r
+\r
+@Component({\r
+  selector: 'app-multi-line-chart',\r
+  templateUrl: './multi-line-chart.component.pug',\r
+  styleUrls: ['./multi-line-chart.component.scss']\r
+})\r
+export class MultiLineChartComponent implements OnInit {\r
+\r
+  @ViewChild('multilinechartdiv') MultiLineChartDiv: ElementRef;\r
+  @Input() height: string;\r
+\r
+  public chart: am4charts.XYChart;\r
+  public loadingIndicator;\r
+  public chartData;\r
+  protected stats: StatsService;\r
+  public dataIsEmpty = 0;\r
+  constructor(private statsService: StatsService) {\r
+    this.stats = statsService;\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.renderChart();\r
+      this.hideLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+      this.renderChart();\r
+      this.hideLoadingIndicator();\r
+    })\r
+    this.renderChart();\r
+\r
+    //Resize if screen size changes.\r
+    // this.stats.checkWindow().subscribe(res=>{\r
+    //   this.renderChart();\r
+    // })\r
+  }\r
+\r
+\r
+  // Rearrange the data to match the format needed for amcharts. Need to group by date. Each object has a date and a list of the lines and its count.\r
+  reformatData() {\r
+    var newData = [];\r
+\r
+    //Names of the test instances. \r
+    var InstanceStrings = {};\r
+\r
+    //Go through the instances and add the names to the InstanceStrings Array. \r
+    for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
+      var instanceName = this.chartData[numberInstances].testInstanceName;\r
+      InstanceStrings[instanceName] = 0;\r
+    }\r
+\r
+    // Iterate through the test instances. \r
+    for (var instanceLength = 0; instanceLength < this.chartData.length; instanceLength++) {\r
+\r
+      //For each instance go through the dates. \r
+      for (var numDates = 0; numDates < this.chartData[instanceLength].dateData.length; numDates++) {\r
+\r
+        //Check newData to see if date has been pushed. \r
+        var dateIndex = newData.findIndex((element) => {\r
+          return (\r
+            this.chartData[instanceLength].dateData[numDates].date.getFullYear() === element.date.getFullYear() &&\r
+            this.chartData[instanceLength].dateData[numDates].date.getMonth() === element.date.getMonth() &&\r
+            this.chartData[instanceLength].dateData[numDates].date.getDate() === element.date.getDate()\r
+          )\r
+        });\r
+\r
+        //If date is not present push the new date and the count for the test instance. \r
+        if (newData[dateIndex] == undefined) {\r
+          //date is not present\r
+          var newDate = {\r
+            date: new Date(this.chartData[instanceLength].dateData[numDates].date.getFullYear(),\r
+              this.chartData[instanceLength].dateData[numDates].date.getMonth(),\r
+              this.chartData[instanceLength].dateData[numDates].date.getDate())\r
+          };\r
+          newDate = Object.assign(newDate, InstanceStrings);\r
+          newDate[this.chartData[instanceLength].testInstanceName] = this.chartData[instanceLength].dateData[numDates].count;\r
+          newData.push(newDate);\r
+        } else {\r
+\r
+          //If the date is present update the count for that test instance. \r
+          newData[dateIndex][this.chartData[instanceLength].testInstanceName] += this.chartData[instanceLength].dateData[numDates].count;\r
+        }\r
+      }\r
+    }\r
+    return newData;\r
+  }\r
+\r
+  //fill in dates that have no data. If a specific date is not present, we need to fill in the date and set a count for 0. \r
+  async setupPoints(rawData): Promise<any> {\r
+    let formattedData = [];\r
+\r
+    //If the chart is supposed to be empty push in a line with a count of 0.\r
+    if (rawData.length == 0) {\r
+      return new Promise((resolve, reject) => {\r
+\r
+        let days = this.daysDuration(this.stats.filters.startDate, this.stats.filters.endDate);\r
+\r
+        if (!days) {\r
+          days = 62;\r
+          this.stats.filters.startDate = (moment().subtract(2, "months").toDate());\r
+        }\r
+\r
+        // Go through 62 days and push a count of 0/\r
+        for (let day = 0; day < days; day++) {\r
+          let newDate = this.addDaysToDate(this.stats.filters.startDate, day);\r
+          formattedData.push({\r
+            date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+            count: 0,\r
+            testInstancename: "empty"\r
+          })\r
+        }\r
+        resolve(formattedData);\r
+      })\r
+\r
+\r
+\r
+\r
+\r
+      //Data is not empty. push in empty days for each instance. Some instances might not have executions for each day. \r
+    } else return new Promise((resolve, reject) => {\r
+      //get list of test instances. \r
+      var InstanceStrings = {};\r
+      for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
+        var instanceName = this.chartData[numberInstances].testInstanceName;\r
+        InstanceStrings[instanceName] = 0;\r
+      }\r
+\r
+\r
+      //Go through the data\r
+      for (let i = 0; i < rawData.length; i++) {\r
+\r
+        //for the first iteration, \r
+        if (i == 0) {\r
+          formattedData.push(rawData[0]);\r
+\r
+          // if the date is before the startDate specified by the filter or two months be default. \r
+          if (formattedData[0].date > this.stats.filters.startDate) {\r
+\r
+            // Go through the difference in days and push the date and count of 0. \r
+            let days = this.daysDuration(this.stats.filters.startDate, formattedData[0].date)\r
+            for (let k = days - 1; k >= 0; k--) {\r
+              let newDate = this.addDaysToDate(this.stats.filters.startDate, k);\r
+              var objectToAdd = {\r
+                date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+              };\r
+              //push the new date and all the strings for the test instances.\r
+              objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+\r
+              //add date to the beginning of the array. \r
+              formattedData.unshift(objectToAdd)\r
+\r
+            }\r
+          }\r
+\r
+          //for all other iterations\r
+        } else {\r
+\r
+          //get the difference in days.\r
+          let days = this.daysDuration(rawData[i].date, rawData[i - 1].date);\r
+          if (days > 1) {\r
+            //push the new dates. \r
+            for (let j = 1; j < days; j++) {\r
+              let newDate = this.addDaysToDate(rawData[i - 1].date, j);\r
+              var objectToAdd = {\r
+                date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+              };\r
+              //push the new date and all the strings for the test instances.\r
+              objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+              formattedData.push(objectToAdd);\r
+            }\r
+          }\r
+          formattedData.push(rawData[i]);\r
+        }\r
+      }\r
+\r
+      if (rawData.length >= 1) {\r
+        var days = this.daysDuration(rawData[rawData.length - 1].date, this.stats.filters.endDate);\r
+        if (days >= 1) {\r
+          for (let j = 1; j < days; j++) {\r
+            let newDate = this.addDaysToDate(rawData[rawData.length - 1].date, j);\r
+            var objectToAdd = {\r
+              date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+            };\r
+            objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+            formattedData.push(objectToAdd);\r
+          }\r
+        }\r
+      }\r
+\r
+\r
+      resolve(formattedData);\r
+    })\r
+  }\r
+\r
+  daysDuration(date1, date2) {\r
+    return Math.ceil(Math.abs((date1 - date2) / (60 * 60 * 24 * 1000)));\r
+  }\r
+\r
+  addDaysToDate(date, days) {\r
+    let newDate = new Date(date);\r
+    newDate.setDate(date.getDate() + days);\r
+    return newDate;\r
+  }\r
+\r
+  //initialize loading indicator\r
+  showLoadingIndicator() {\r
+\r
+    this.height = "380px";\r
+    this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+    this.loadingIndicator.background.fill = am4core.color("#fff");\r
+    this.loadingIndicator.background.fillOpacity = 0.8;\r
+    this.loadingIndicator.width = am4core.percent(100);\r
+    this.loadingIndicator.height = am4core.percent(100);\r
+\r
+    let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+    indicatorLabel.text = "Loading..";\r
+    indicatorLabel.align = "center";\r
+    indicatorLabel.valign = "middle";\r
+    indicatorLabel.fontSize = 18;\r
+    indicatorLabel.fontWeight= "bold";\r
+    indicatorLabel.dy = 50;\r
+\r
+    let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+    //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+    loadingImage.href = "https://loading.io/spinners/equalizer/lg.equalizer-bars-loader.gif";\r
+    //loadingImage.dataSource = "/loading-pies.svg"\r
+    loadingImage.align = "center";\r
+    loadingImage.valign = "middle";\r
+    loadingImage.horizontalCenter = "middle";\r
+    loadingImage.verticalCenter = "middle";\r
+    loadingImage.scale = 3.0;\r
+\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  async renderChart() {\r
+    //console.log("here")\r
+\r
+    am4core.options.minPolylineStep = 5;\r
+\r
+    this.chart = am4core.create(this.MultiLineChartDiv.nativeElement, am4charts.XYChart);\r
+    this.chart.hiddenState.properties.opacity = 0; // this creates initial fade-in\r
+\r
+    this.chart.paddingRight = 20;\r
+    this.chartData = this.stats.getData("multiLineData");\r
+\r
+    //reformat the data to match the format needed for amcharts. \r
+    var formattedData = this.reformatData();\r
+\r
+    //sort the data.\r
+    formattedData.sort((a, b) => a.date - b.date);\r
+\r
+    //fill in gaps in the data\r
+    await this.setupPoints(formattedData).then(res => {\r
+      formattedData = res;\r
+    }, err => console.log(err));\r
+\r
+    this.chart.data = formattedData;\r
+\r
+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
+    dateAxis.title.text = "Date";\r
+\r
+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.title.text = "Executions";\r
+\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+    this.chart.cursor.xAxis = dateAxis;\r
+\r
+    //if the data is empty, push in a line and set the count to 0. \r
+    if (this.chartData.length == 0) {\r
+      this.chartData.push({ testInstanceName: "empty" })\r
+      let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
+      newSeries.name = "empty";\r
+      newSeries.dataFields.dateX = "date";\r
+      newSeries.dataFields.valueY = "count";\r
+      newSeries.tooltipText = "{valueY.value}";\r
+      newSeries.sequencedInterpolation = true;\r
+      newSeries.defaultState.transitionDuration = 1000;\r
+      newSeries.strokeWidth = 3;\r
+      newSeries.tensionX = 0.8;\r
+    } else {\r
+\r
+      //initialize the lines for the series\r
+      for (let index = 0; index < this.chartData.length; index++) {\r
+        let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
+        newSeries.name = this.chartData[index].testInstanceName;\r
+        newSeries.dataFields.dateX = "date";\r
+        newSeries.dataFields.valueY = (this.chartData[index].testInstanceName).toString();\r
+        newSeries.tooltipText = "{valueY.value}";\r
+        newSeries.sequencedInterpolation = true;\r
+        newSeries.defaultState.transitionDuration = 1000;\r
+        newSeries.strokeWidth = 3;\r
+        newSeries.tensionX = 0.8;\r
+      }\r
+      this.chart.legend = new am4charts.Legend();\r
+      this.chart.legend.labels.template.text = "[bold {color}]{name}";\r
+    }\r
+  }\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.pug
new file mode 100644 (file)
index 0000000..0456731
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#pieChartDiv, [style.height]="height")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..28b4c37
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { PieChartComponent } from './pie-chart.component';\r
+\r
+describe('PieChartComponent', () => {\r
+  let component: PieChartComponent;\r
+  let fixture: ComponentFixture<PieChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ PieChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(PieChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.ts
new file mode 100644 (file)
index 0000000..16e7166
--- /dev/null
@@ -0,0 +1,181 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, NgZone, Input, ViewChild, ElementRef, OnDestroy, } from '@angular/core';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import { StatsService } from '../stats.service';\r
+import { Router } from '@angular/router';\r
+import { Subscription } from 'rxjs';\r
+\r
+//am4core.useTheme(frozen);\r
+//am4core.useTheme(am4themes_animated);\r
+\r
+@Component({\r
+  selector: 'app-pie-chart',\r
+  templateUrl: './pie-chart.component.pug',\r
+  styleUrls: ['./pie-chart.component.scss']\r
+})\r
+export class PieChartComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  @ViewChild('pieChartDiv') PieChartDiv: ElementRef;\r
+  @ViewChild('legendDiv') legendDiv: ElementRef;\r
+  @Input() height: string;\r
+\r
+  protected stats: StatsService;\r
+  public chart: am4charts.PieChart;\r
+  private chartData: Array<Object>;\r
+  public loadingIndicator;\r
+\r
+  constructor(private statsService: StatsService, private route: Router) {\r
+    this.stats = statsService;\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.renderChart();\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData()\r
+    }));\r
+    \r
+\r
+    // //Resize if screen size changes.\r
+    // this.stats.checkWindow().subscribe(res=>{\r
+    //   this.renderChart();\r
+    // })\r
+\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.toDestroy.forEach(e => e.unsubscribe());\r
+    this.chart.dispose();\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+\r
+    if(!this.loadingIndicator){\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight= "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    }else{\r
+      this.loadingIndicator.show();\r
+    }\r
+    \r
+\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  setChartData(){\r
+    this.chartData = this.stats.getData("TD_Results") as Array<Object>;\r
+    if (this.chartData.length == 0) {\r
+      this.chart.data = [{\r
+        Name: "N/A",\r
+        Count: 1,\r
+      }]\r
+      \r
+      this.chart.series.values[0].tooltipText = "No Executions Found"\r
+    } else {\r
+      this.chart.data = this.chartData;\r
+      //OnClick open page for that result. \r
+      this.chart.series.values[0].slices.template.events.on("doublehit", (clickedSlice) => {\r
+        this.route.navigate(['/test-executions', { filter: clickedSlice.target.dataItem.dataContext['Name'].toString().toLowerCase() }]);\r
+      });\r
+    }\r
+    this.hideLoadingIndicator();\r
+    \r
+  }\r
+\r
+  renderChart() {\r
+\r
+    this.chart = am4core.create(this.PieChartDiv.nativeElement, am4charts.PieChart);\r
+    let series = this.chart.series.push(new am4charts.PieSeries());\r
+    this.chart.scale = 1;\r
+    this.chart.align = "center";\r
+    this.showLoadingIndicator();\r
+\r
+    // this.chart.legend = new am4charts.Legend();\r
+    // this.chart.legend.position = "right";\r
+    // this.chart.legend.scale = .7;\r
+    // this.chart.legend.markers.template.width = 10;\r
+    // this.chart.legend.markers.template.height = 10;\r
+  \r
+    //chart.preloader.disabled = false;\r
+    //chart.hiddenState.properties.opacity = 0; // this creates initial fade-in\r
+    \r
+    // var legendContainer = am4core.create(this.legendDiv.nativeElement, am4core.Container);\r
+    // legendContainer.width = am4core.percent(100);\r
+    // legendContainer.height = am4core.percent(100);\r
+    // this.chart.legend.parent = legendContainer;\r
+    series.dataFields.value = "Count";\r
+    series.dataFields.category = "Name";\r
+    series.slices.template.strokeWidth = 1;\r
+    series.slices.template.strokeOpacity = 1;\r
+    series.slices.template.propertyFields.fill = "color"\r
+    series.scale = .8;\r
+\r
+    // This creates initial animation\r
+    series.hiddenState.properties.opacity = 1;\r
+    series.hiddenState.properties.endAngle = -90;\r
+    series.hiddenState.properties.startAngle = -90;\r
+    series.ticks.template.disabled = false;\r
+    series.labels.template.disabled = false;\r
+    series.titleElement.textContent = 'Total Test Results'\r
+\r
+    //responsive pie chart. if size of chart is less than 450 pixels remove legend. \r
+    this.chart.responsive.enabled = true;\r
+    \r
+    \r
+\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.pug b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.pug
new file mode 100644 (file)
index 0000000..b612b28
--- /dev/null
@@ -0,0 +1,34 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+hr\r
+div(*ngIf=dataSource).container\r
+  table(mat-table [dataSource]="dataSource")\r
+    ng-container(matColumnDef="name")\r
+      th(mat-header-cell *matHeaderCellDef) Name\r
+      td(mat-cell *matCellDef='let element') {{element.name}}\r
+\r
+    ng-container(matColumnDef="dateExec")\r
+      th(mat-header-cell *matHeaderCellDef) Date\r
+      td(mat-cell *matCellDef='let element') {{element.dateExec}}\r
+\r
+    ng-container(matColumnDef="timeExec")\r
+      th(mat-header-cell *matHeaderCellDef) Time\r
+      td(mat-cell *matCellDef='let element') {{element.timeExec}}\r
+\r
+\r
+    tr(mat-header-row *matHeaderRowDef="displayedColumns")\r
+    tr(mat-row *matRowDef="let row; columns: displayedColumns;")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.scss b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.scss
new file mode 100644 (file)
index 0000000..1291718
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.container{\r
+    overflow: auto;\r
+}\r
+\r
+table{\r
+    height: 100%;\r
+    width: 100%;\r
+}\r
+\r
+td, th{\r
+    font-size: 14px;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.spec.ts
new file mode 100644 (file)
index 0000000..14cb457
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ScheduleComponent } from './schedule.component';\r
+\r
+describe('ScheduleComponent', () => {\r
+  let component: ScheduleComponent;\r
+  let fixture: ComponentFixture<ScheduleComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ScheduleComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ScheduleComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.ts b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.ts
new file mode 100644 (file)
index 0000000..a8a04ce
--- /dev/null
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, NgZone, ChangeDetectorRef } from '@angular/core';\r
+//import material from "@amcharts/amcharts4/themes/material";\r
+import am4themes_animated from "@amcharts/amcharts4/themes/animated";\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { StatsService } from '../stats.service';\r
+import { Observable, Subject } from 'rxjs';\r
+\r
+export interface ScheduleElement {\r
+  name: string;\r
+  dateExec: string;\r
+  timeExec: string;\r
+}\r
+\r
+@Component({\r
+  selector: 'app-schedule',\r
+  templateUrl: './schedule.component.pug',\r
+  styleUrls: ['./schedule.component.scss']\r
+})\r
+\r
+export class ScheduleComponent implements OnInit {\r
+\r
+  protected stats: StatsService;\r
+  public doneLoadingfalse;\r
+  public dataSource;\r
+\r
+  displayedColumns: string[] = ['name', 'dateExec', 'timeExec'];\r
+\r
+  constructor(private zone: NgZone, private _groups: GroupService, private statsService: StatsService, private changeDetector: ChangeDetectorRef) {\r
+    this.stats = statsService;\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.dataSource = this.stats.getData("Schedule");\r
+    })\r
+    this.dataSource = this.stats.getData("Schedule");\r
+\r
+    this.refresh();\r
+  }\r
+\r
+  defaultDataListener(): Observable<Object> {\r
+    return this.stats.finishedDefaultData;\r
+  }\r
+\r
+  refresh(){\r
+    this.stats.onScheduleChangeFinished().subscribe(res => {\r
+      this.dataSource = this.stats.getData("Schedule");\r
+      this.dataSource = this.dataSource.slice();\r
+      \r
+      this.changeDetector.detectChanges();\r
+    })\r
+  }\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/stats.service.spec.ts b/otf-frontend/client/src/app/layout/components/stats/stats.service.spec.ts
new file mode 100644 (file)
index 0000000..59cd997
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { StatsService } from './stats.service';\r
+\r
+describe('StatsService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: StatsService = TestBed.get(StatsService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/stats.service.ts b/otf-frontend/client/src/app/layout/components/stats/stats.service.ts
new file mode 100644 (file)
index 0000000..31d872c
--- /dev/null
@@ -0,0 +1,765 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { FeathersService } from 'app/shared/services/feathers.service';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+import { Observable, Subject, from } from 'rxjs';\r
+import { MatDialog } from '@angular/material';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import * as moment from 'moment';\r
+import { SchedulingService } from 'app/shared/services/scheduling.service';\r
+import { TestInstanceService } from 'app/shared/services/test-instance.service';\r
+import { string, number } from '@amcharts/amcharts4/core';\r
+import { group } from '@angular/animations';\r
+export interface StatsFilter {\r
+  startDate?: Date,\r
+  endDate?: Date,\r
+  selectedTestDefinitions?: Array<any>,\r
+  selectedTestInstances?: Array<any>\r
+}\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+\r
+//this service serves as the controller between the dashboard's data management and the components' UI.\r
+export class StatsService {\r
+\r
+  //set default filters\r
+  public filters: StatsFilter = {\r
+    startDate: moment().subtract(1, 'weeks').toDate(),\r
+    endDate: moment().toDate()\r
+  }\r
+\r
+  public executionList: Array<any> = [];\r
+\r
+  public testDefinitionData = {\r
+    //Executions Array thats made of objects with date, test definition name, and count\r
+    "Executions": [],\r
+    //Array of Results for the Pie Chart\r
+    "Results": [],\r
+  }\r
+  public testInstanceData = {\r
+    //Executions for Each Test Instance\r
+    "Executions": [],\r
+    //For multilinechart, objects made of test instance names, and execution count for each one.\r
+    "Individual_Exec": []\r
+  };\r
+\r
+  //list if test instance names\r
+  public testInstances = [];\r
+  //list of scheduled tests gotten from agenda in db.\r
+  public scheduledTests = [];\r
+\r
+  //these are filter objects attached to stats service that are updated whenever user confirms from filter modal. \r
+  //They are updated in the filter functions below.\r
+  // public tdFilters = {\r
+  //   startDate: {},\r
+  //   endDate: {},\r
+  //   selected: [],\r
+  // }\r
+  // public tiFilters = {\r
+  //   startDate: {},\r
+  //   endDate: {},\r
+  //   selectedTDs: [],\r
+  //   selectedTIs: [],\r
+  //   //this limit is for the amount of multiple series being displayed on the multiline chart according to default/filters.\r
+  //   multiLineLimit: 5,\r
+  // }\r
+  // public scheduleFilters = {\r
+  //   startDate: {},\r
+  //   endDate: {},\r
+  //   timeRangeStart: {},\r
+  //   timeRangeEnd: {},\r
+  //   selectedInstances: [],\r
+  // }\r
+\r
+  //these are for triggering the listeners located in the chart components.\r
+  //executionsChange subjects are triggered when user filters.\r
+  public tdExecutionsChange: Subject<Object> = new Subject<Object>();\r
+  public tiExecutionsChange: Subject<Object> = new Subject<Object>();\r
+  public scheduleChange: Subject<Object> = new Subject<Object>();\r
+  public finishedDefaultData: Subject<Object> = new Subject<Object>();\r
+  public startDefaultData: Subject<Object> = new Subject<Object>();\r
+  public startTDExecutionCall: Subject<Object> = new Subject<Object>();\r
+  public startTIExecutionCall: Subject<Object> = new Subject<Object>();\r
+  public windowResized: Subject<Object> = new Subject<Object>();\r
+  public startScheduleCall: Subject<Object> = new Subject<Object>();\r
+\r
+  constructor(public feathers: FeathersService, public testExecution: TestExecutionService,\r
+    public _groups: GroupService, public testInstanceService: TestInstanceService, public schedService: SchedulingService) {\r
+\r
+    //listening for whether user changes group, if so, variables are reset (rest are inside defaultData, we can work on consistency).\r
+    //and we get default data for the new group passed in.\r
+    // this.getDefaultData(this._groups.getGroup());\r
+    // this._groups.groupChange().subscribe(group => {\r
+    //   this.getDefaultData(group);\r
+    // });\r
+\r
+  }\r
+\r
+  \r
+\r
+  //these are trigger functions that we call in the data manipulating functions below.\r
+  //the purpose of these functions is to let the listeners set up in the chart components know that the data has been updated.\r
+  //then the chart components recall the data using getData().\r
+  checkWindow() {\r
+    return this.windowResized;\r
+  }\r
+\r
+  onDefaultDataCallStarted() {\r
+    return this.startDefaultData;\r
+  }\r
+\r
+  onTDExecutionChangeStarted() {\r
+    return this.startTDExecutionCall;\r
+  }\r
+\r
+  onTIExecutionChangeStarted() {\r
+    return this.startTIExecutionCall;\r
+  }\r
+\r
+  onScheduleChangeStarted() {\r
+    return this.startScheduleCall;\r
+  }\r
+\r
+  onDefaultDataCallFinished() {\r
+    return this.finishedDefaultData;\r
+  }\r
+\r
+  onTDExecutionChangeFinished() {\r
+    return this.tdExecutionsChange;\r
+  }\r
+\r
+  onTIExecutionChangeFinished() {\r
+    return this.tiExecutionsChange;\r
+  }\r
+\r
+  onScheduleChangeFinished() {\r
+    return this.scheduleChange;\r
+  }\r
+\r
+  //one giant getter where we pass in the name of what we want and switch case it.\r
+\r
+  //This function is called in the components and returns the relavent array for the component.\r
+  getData(name: string) {\r
+    let outputData = {};\r
+\r
+    switch (name) {\r
+      case "TD_Executions":\r
+        return this.testDefinitionData.Executions;\r
+      case "TD_Results":\r
+        return this.testDefinitionData.Results;\r
+      case "testInstances":\r
+        return this.testInstanceData.Executions;\r
+      case "Schedule":\r
+        return this.scheduledTests;\r
+      // case "multiLineData":\r
+      //   //limitting the series being displayed.\r
+      //   return this.testInstanceData.Individual_Exec.slice(0, this.tiFilters.multiLineLimit);\r
+    }\r
+    //console.log(outputData);\r
+    return outputData;\r
+  }\r
+\r
+  //this gets called from the filter modal when the user confirms their filters.\r
+  filterData(allFilters, tdFilters, tiFilters, schedFilters) {\r
+    //this.filterAll(allFilters);\r
+    this.filterTDs(tdFilters);\r
+    this.filterTIs(tiFilters);\r
+    this.filterSchedule(schedFilters)\r
+  }\r
+\r
+  //this is still under the works, the purpose of this is to filter ALL the components of the dashboard if they have common filtering grounds.\r
+  filterAll(allFilters) {\r
+    //console.log('Filtering everything')\r
+    //console.log(allFilters);\r
+    if (allFilters.startDate != "" || allFilters.endDate != "") {\r
+      if (allFilters.endDate == "") {\r
+        this.testDefinitionData.Executions = this.testDefinitionData.Executions.filter(execution => (\r
+          execution.startTime >= allFilters.startDate\r
+        ))\r
+      } else if (allFilters.startDate == "") {\r
+        this.testDefinitionData.Executions = this.testDefinitionData.Executions.filter(execution => (\r
+          execution.startTime <= allFilters.endDate\r
+        ))\r
+      } else {\r
+        this.testDefinitionData.Executions = this.testDefinitionData.Executions.filter(execution => (\r
+          execution.startTime >= allFilters.startDate &&\r
+          execution.date <= allFilters.endDate\r
+        ))\r
+      }\r
+    }\r
+  }\r
+\r
+  /*\r
+    this function takes in test definition filters and queries data accordingly.\r
+    improvement needed: if the filters provided do not require querying at all, the function should narrow the currently existing data. This\r
+    will be faster than requerying in those cases and improve loading times.\r
+  */\r
+  async filterTDs(tdFilters) {\r
+\r
+    /*\r
+      checking if the filters passed in are empty, if so do nothing, if not, trigger a start call that lets the components know querying is going to begin.\r
+      these start..Call() functions are so chart components can turn on their loading indicators.\r
+    */\r
+    // if (tdFilters.startDate == "" && tdFilters.endDate == "" && tdFilters.selected.length == 0) return;\r
+    // else this.startTDExecutionCall.next(tdFilters);\r
+\r
+    // //updating filter objects attached to stats service so we can use the service getters to get them where we want in the charts component code.\r
+    // this.tdFilters = tdFilters;\r
+\r
+    // //if no range is passed in we use the default range of past 2 months.\r
+    // let startDate = tdFilters.startDate == "" ? new Date(moment().subtract(2, 'weeks').format('L')) : new Date(tdFilters.startDate);\r
+    // let endDate = tdFilters.endDate == "" ? moment().toDate() : new Date(tdFilters.endDate);\r
+    // //update service filters accordingly. \r
+    // this.tdFilters.startDate = startDate;\r
+    // this.tdFilters.endDate = endDate;\r
+    //variable of id's of the test definitions selected in the filters.\r
+    let selectedIDs = this.filters.selectedTestDefinitions.map(item => item.id);\r
+\r
+    //Promise that queries the data according the filters. We use a promise so we wait for the data to query before the components render.\r
+    await new Promise((resolve, reject) => {\r
+\r
+      //feathers query time.\r
+      this.testExecution.find({\r
+        //get the data relevant to the group.\r
+        groupId: this._groups.getGroup()["_id"],\r
+        //thse are gonna go in the data objects.\r
+        $select: [\r
+          'historicTestDefinition.testName',\r
+          'startTime',\r
+          'testResult'\r
+        ],\r
+        //UNLIMITED DATA BABY.\r
+        $limit: -1,\r
+        //sort according to ascending dates.\r
+        $sort: {\r
+          startTime: 1,\r
+        },\r
+        //select the start and end dates from the filter dates.\r
+        startTime: {\r
+          $gte: this.filters.startDate,\r
+          $lte: this.filters.endDate,\r
+        },\r
+        //select the test definitions according to the selected ones in the filters.\r
+        'historicTestDefinition._id': {\r
+          $in: selectedIDs\r
+        }\r
+      }).subscribe(result => {\r
+\r
+        //console.log(result)\r
+\r
+        //resetting real quick cuz why not.\r
+        this.testDefinitionData = {\r
+          "Executions": [],\r
+          "Results": [],\r
+        }\r
+\r
+        //pretty self explanitory.\r
+        let fetchedData = result as Array<any>;\r
+        let currentExecutionsData = this.testDefinitionData.Executions;\r
+\r
+        /*\r
+          for each new fetched json we got with the selected stuff we specified in the feathers query,\r
+          we need to organize and distribute them accordingly to our service's data objects. For example, the json objects consist of\r
+          'historicTestDefinition.testName',\r
+          'startTime',\r
+          'testResult',\r
+          test results belong in the results array, the rest needs to be organzied in the executions array,\r
+          thats what the populate methods are for.\r
+        */\r
+        for (let index in fetchedData) {\r
+          let newItem = fetchedData[index];\r
+\r
+          //for consistency we're supposed to pass current data to both we'll fix that later, but for now the piechart one just calls the current results data\r
+          //inside itself.\r
+          this.populateLineChartData(newItem, currentExecutionsData);\r
+          this.populatePieChartData(newItem);\r
+        }\r
+        resolve();\r
+      })\r
+    }).then(res => {\r
+      //console.log(res);\r
+\r
+      //trigger that querying is done and the test definition executions data has been changed. Line chart and pie chart listen for this.\r
+      this.tdExecutionsChange.next(res);\r
+    })\r
+  }\r
+\r
+  //similar stuffies. just small differences.\r
+  async filterTIs(tiFilters) {\r
+\r
+    // if (tiFilters.startDate == "" && tiFilters.endDate == "" && tiFilters.selectedTDs.length == 0 && tiFilters.selectedTIs.length == 0) return;\r
+    // else this.startTIExecutionCall.next(tiFilters);\r
+\r
+    // this.tiFilters = tiFilters;\r
+    // if (tiFilters.selectedTIs.length > 0 && tiFilters.selectedTDs.length > 0) this.tiFilters.multiLineLimit = tiFilters.selectedTIs.length + tiFilters.selectedTDs.length;\r
+    // else if (tiFilters.selectedTIs.length > 0) this.tiFilters.multiLineLimit = tiFilters.selectedTIs.length;\r
+    // else if (tiFilters.selectedTDs.length > 0) this.tiFilters.multiLineLimit = tiFilters.selectedTDs.length;\r
+    // else this.tiFilters.multiLineLimit = 5;\r
+\r
+    // let startDate = tiFilters.startDate == "" ? new Date(moment().subtract(2, 'weeks').format('L')) : new Date(tiFilters.startDate);\r
+    // let endDate = tiFilters.endDate == "" ? moment().toDate() : new Date(tiFilters.endDate);\r
+\r
+    // this.tiFilters.startDate = startDate;\r
+    // this.tiFilters.endDate = endDate;\r
+    // console.log(tiFilters.selectedTDs)\r
+\r
+    await new Promise((resolve, reject) => {\r
+      this.testExecution.find({\r
+        groupId: this._groups.getGroup()["_id"],\r
+        $limit: -1,\r
+        startTime: {\r
+          $gte: this.filters.startDate,\r
+          $lte: this.filters.endDate,\r
+        },\r
+        $select: [\r
+          'startTime',\r
+          'endTime',\r
+          'historicTestDefinition.testName',\r
+          'historicTestInstance.testInstanceName',\r
+        ],\r
+        $or: [\r
+          {\r
+            'historicTestDefinition._id': {\r
+              $in: tiFilters.selectedTDs\r
+            }\r
+          },\r
+          {\r
+            'historicTestInstance._id': {\r
+              $in: tiFilters.selectedTIs\r
+            }\r
+          }\r
+        ]\r
+\r
+      }).subscribe(result => {\r
+        this.testInstanceData = {\r
+          "Executions": [],\r
+          "Individual_Exec": []\r
+        }\r
+        //console.log(result)\r
+        let fetchedData = result as Array<any>;\r
+        for (let index in fetchedData) {\r
+          let newItem = fetchedData[index];\r
+          this.populateBarChartData(newItem);\r
+          this.populateMultiLineChartData(newItem);\r
+        }\r
+        this.testInstanceData.Executions.sort((a, b) => b.Count - a.Count);\r
+        this.testInstanceData.Individual_Exec.sort((a, b) => b.total - a.total);\r
+\r
+        resolve();\r
+      })\r
+    }).then(res => {\r
+      this.tiExecutionsChange.next(res);\r
+      //console.log(this.testInstanceData.Executions);\r
+    })\r
+\r
+  }\r
+\r
+  //similar stuffies just smol differneces.\r
+  async filterSchedule(schedFilters) {\r
+\r
+    //console.log(schedFilters);\r
+    // this.scheduleFilters = schedFilters;\r
+    // //console.log(schedFilters.selectedInstances);\r
+\r
+    // if (schedFilters.startDate == "" &&\r
+    //   schedFilters.endDate == "" &&\r
+    //   schedFilters.selectedInstances.length == 0) {\r
+    //   return;\r
+    // } else this.startScheduleCall.next(schedFilters);\r
+\r
+    // let startDate = schedFilters.startDate == "" ? new Date(moment().toDate()) : new Date(schedFilters.startDate);\r
+    // let endDate = schedFilters.endDate == "" ? new Date(moment().add(2, 'weeks').format('L')) : new Date(schedFilters.endDate);\r
+\r
+    // this.scheduleFilters.startDate = startDate;\r
+    // this.scheduleFilters.endDate = endDate;\r
+\r
+    \r
+    this.schedService.find({\r
+      $select: ["data.testSchedule._testInstanceId", 'nextRunAt'],\r
+      $limit: -1,\r
+    }).subscribe(result => {\r
+      this.scheduledTests = [];\r
+      //console.log(result);\r
+      let fetchedData = result as Array<any>;\r
+      let resultingData: Array<any> = fetchedData;\r
+      if (schedFilters.selectedInstances.length !== 0) {\r
+        resultingData = fetchedData.filter(el => {\r
+          let fetchedID = el.data.testSchedule._testInstanceId;\r
+          let selectedIDs = schedFilters.selectedInstances as Array<any>;\r
+          let condition = selectedIDs.includes(fetchedID.toString());\r
+          //console.log(condition);\r
+          return condition;\r
+        })\r
+      }\r
+\r
+      resultingData = resultingData.filter(el => {\r
+        let schedDate = new Date(el.nextRunAt);\r
+        return schedDate >= this.filters.startDate && schedDate <= this.filters.endDate;\r
+      })\r
+\r
+      for (let index in resultingData) {\r
+        let checkIfTestBelongsToUserGroup = this.testInstances.findIndex(testInstances => testInstances.id === resultingData[index].data.testSchedule._testInstanceId);\r
+        if (checkIfTestBelongsToUserGroup >= 0) {\r
+          if (resultingData[index].nextRunAt) {\r
+            let d1 = new Date(resultingData[index].nextRunAt);\r
+            this.scheduledTests.push({\r
+              id: resultingData[index].data.testSchedule._testInstanceId,\r
+              name: this.testInstances[checkIfTestBelongsToUserGroup].name,\r
+              dateExec: d1.toDateString(),\r
+              timeExec: d1.toLocaleTimeString()\r
+            })\r
+          }\r
+        }\r
+      }\r
+      this.scheduleChange.next();\r
+    });\r
+    \r
+\r
+\r
+  }\r
+\r
+  //getters for the filter objects.\r
+  // getTDFilters() {\r
+  //   return this.tdFilters;\r
+  // }\r
+\r
+  // getTIFilters() {\r
+  //   return this.tiFilters;\r
+  // }\r
+\r
+  // getSchedFilters() {\r
+  //   return this.scheduleFilters;\r
+  // }\r
+\r
+  calcTime(execution) {\r
+    var end = new Date(execution.endTime);\r
+    var start = new Date(execution.startTime);\r
+    var executionTime = (end.getTime() - start.getTime()) / 1000;\r
+    return executionTime;\r
+  }\r
+\r
+  //This function takes an execution that was retrieved from the Database and takes the data it needs for the line chart. \r
+  populateLineChartData(execution, currentData) {\r
+    let executionDate = new Date(execution.startTime)\r
+\r
+    // Looks to see if the date already has an execution./\r
+    let indexOfItemFound = currentData.findIndex((element) => {\r
+\r
+      return (\r
+        executionDate.getFullYear() === element.date.getFullYear() &&\r
+        executionDate.getMonth() === element.date.getMonth() &&\r
+        executionDate.getDate() === element.date.getDate()\r
+      )\r
+    })\r
+\r
+    //If the date is not found. Push a new date into the array with a count of one\r
+    if (currentData[indexOfItemFound] == undefined) {\r
+      currentData.push({\r
+        date: new Date(executionDate.getFullYear(), executionDate.getMonth(), executionDate.getDate()),\r
+        count: 1\r
+      })\r
+    // else update the count\r
+    } else currentData[indexOfItemFound].count += 1;\r
+  }\r
+\r
+\r
+  //Takes an execution and  pushes the result/count or updates the count. For the Pie Chart\r
+  populatePieChartData(execution) {\r
+\r
+    //Check if result is already present in the array. \r
+    var checkIfPresent = this.testDefinitionData.Results.find(Results => Results.Name === execution.testResult);\r
+   \r
+    //If not present, add it to TOPSTATs with a default count of 1.    \r
+    if (!checkIfPresent) {\r
+      \r
+      var color;\r
+      //Set the color for the pie chart.\r
+      if (execution.testResult == "COMPLETED"){\r
+        color = "#0D47A1";\r
+      }else if (execution.testResult == "FAILED")\r
+        color = "#DD2C00";\r
+      else if (execution.testResult == "UNKNOWN")\r
+        color = "#C4CBD4";\r
+      else if (execution.testResult == "SUCCESS")\r
+        color = "#42d660";\r
+      else if (execution.testResult == "success")\r
+        color = "#42d660";\r
+      else if (execution.testResult == "STARTED")\r
+        color = "#29E3E8";\r
+      else if (execution.testResult == "FAILURE")\r
+        color = "#FC9100";\r
+      else if (execution.testResult == "STOPPED")\r
+        color = "#900C3F";\r
+      else if (execution.testResult == "TERMINATED")\r
+        color = "#AC00FC";\r
+      else if (execution.testResult == "UNAUTHORIZED")\r
+        color = "#7A6E6E";\r
+      else if (execution.testResult == "DOES_NOT_EXIST")\r
+        color = "#000000";\r
+      else if (execution.testResult == "ERROR")\r
+        color = "#eb2acd"\r
+      else if (execution.testResult == "WORKFLOW_ERROR")\r
+        color = "#194f24"\r
+      else\r
+        color = "#000000".replace(/0/g, function () { return (~~(Math.random() * 16)).toString(16); });\r
+\r
+      //Push the execution with the count and color.\r
+      this.testDefinitionData.Results.push({ Name: execution.testResult, Count: 1, color: color });\r
+    } else {\r
+\r
+      //Find index of the testResult and update the count by 1.\r
+      var position = this.testDefinitionData.Results.findIndex(Results => Results.Name === execution.testResult);\r
+      this.testDefinitionData.Results[position].Count += 1;\r
+    }\r
+  }\r
+\r
+  //Takes an execution and pushes result into the barchart. \r
+  populateBarChartData(execution) {\r
+\r
+    //check if test instance is present in the array. \r
+    var checkIfPresent = this.testInstanceData.Executions.find(Instances => Instances.id === execution.historicTestInstance._id);\r
+\r
+    //calculates the time it took for the execution/\r
+    var executionTime = this.calcTime(execution);\r
+    \r
+    //If execution is not present, push the test instance with a count of 1.\r
+    if (!checkIfPresent) {\r
+      //If not present, add it to testInstanceData with a default count of 1. \r
+\r
+      this.testInstanceData.Executions.push({\r
+        Name: execution.historicTestInstance.testInstanceName,\r
+        id: execution.historicTestInstance._id,\r
+        testResult: execution.testResult,\r
+        executionTime: executionTime,\r
+        Count: 1,\r
+        Average: executionTime\r
+      });\r
+    } else {\r
+      // If Present update count and execution time. \r
+      var position = this.testInstanceData.Executions.findIndex(Instances => Instances.id === execution.historicTestInstance._id);\r
+      this.testInstanceData.Executions[position].Count += 1;\r
+      this.testInstanceData.Executions[position].executionTime += executionTime;\r
+      this.testInstanceData.Executions[position].Average = this.testInstanceData.Executions[position].executionTime / this.testInstanceData.Executions[position].Count;\r
+    }\r
+\r
+  }\r
+\r
+  //queries data for the scheduled tests. \r
+  getScheduledTests(groupId) {\r
+\r
+    //Queries a list of test instances by group ID\r
+    this.testInstanceService.find({\r
+      groupId: groupId['_id'],\r
+      $select: ["_id", "testInstanceName", "groupId"],\r
+      $limit: -1\r
+    }).subscribe(result => {\r
+\r
+      //Iterate through the list and add the test instances to the list. \r
+      for (var index in result) {\r
+        var checkIfPresent = this.testInstances.find(id => id === result[index]._id);\r
+        if (!checkIfPresent)\r
+          this.testInstances.push({ id: result[index]._id, name: result[index].testInstanceName });\r
+      }\r
+    });\r
+\r
+    //Queries all of the scheduled tests. \r
+    this.schedService.find({\r
+\r
+      $select: ["data.testSchedule._testInstanceId", 'nextRunAt'],\r
+      $limit: -1,\r
+      $sort: {\r
+        startTime: 1\r
+      },\r
+\r
+    }).subscribe(result => {\r
+\r
+      this.scheduledTests = [];\r
+      for (var index in result) {\r
+\r
+        //If the scheduled testinstance is owned by the group, push the result. \r
+        var checkIfTestBelongsToUserGroup = this.testInstances.findIndex(testInstances => testInstances.id === result[index].data.testSchedule._testInstanceId);\r
+        if (checkIfTestBelongsToUserGroup >= 0) {\r
+\r
+          //If the next run at is valid, the test is scheduled.\r
+          if (result[index].nextRunAt) {\r
+            let d1 = new Date(result[index].nextRunAt);\r
+            this.scheduledTests.push({\r
+              id: result[index].data.testSchedule._testInstanceId,\r
+              name: this.testInstances[checkIfTestBelongsToUserGroup].name,\r
+              dateExec: d1.toDateString(),\r
+              timeExec: d1.toLocaleTimeString()\r
+            });\r
+          }\r
+        }\r
+      }\r
+    });\r
+  }\r
+\r
+  //populate multi line chart\r
+  populateMultiLineChartData(execution) {\r
+\r
+    let executionDate = new Date(execution.startTime)\r
+    let currentData = this.testInstanceData.Individual_Exec;\r
+    let count = 1;\r
+    //find if Instance is already present in the array. \r
+    let position = this.testInstanceData.Individual_Exec.findIndex(Instances => Instances.id === execution.historicTestInstance._id);\r
+\r
+    //First execution for this instance\r
+    if (currentData[position] == undefined) {\r
+      currentData.push({\r
+        testInstanceName: execution.historicTestInstance.testInstanceName,\r
+        testDefinitionName: execution.historicTestDefinition.testDefintionName,\r
+        id: execution.historicTestInstance._id,\r
+        dateData: [{ date: executionDate, count: count, name: execution.historicTestInstance.testInstanceName }],\r
+        total: 1\r
+      })\r
+      //execution already present\r
+    } else {\r
+      //find index of Date\r
+      let indexOfDate = currentData[position].dateData.findIndex((element) => {\r
+        return (\r
+          executionDate.getFullYear() === element.date.getFullYear() &&\r
+          executionDate.getMonth() === element.date.getMonth() &&\r
+          executionDate.getDate() === element.date.getDate()\r
+        )\r
+      });\r
+\r
+      //Check if the exeuction date is valid for this instance. If it is not present, push a new date and count. \r
+      if (currentData[position].dateData[indexOfDate] == undefined) {\r
+        let count = 1;\r
+        //Push the new Date\r
+        currentData[position].dateData.push({ date: executionDate, count: count, name: execution.historicTestInstance.testInstanceName, id: execution.historicTestInstance._id});\r
+        currentData[position].total++;\r
+      } else {\r
+        //date is already present\r
+        currentData[position].dateData[indexOfDate].count++;\r
+        currentData[position].total++;\r
+      }\r
+    }\r
+  }\r
+  //Gets the initial data for the default page. \r
+  async getDefaultData(group, query?) { \r
+    if(!group){\r
+      return;\r
+    }\r
+    \r
+    this.scheduledTests = [];\r
+\r
+    this.startDefaultData.next(group);\r
+    let groupId = group;\r
+    //let startDate = moment().subtract(2, 'weeks').toDate();\r
+    \r
+    //query sheduled tests\r
+    //this.getScheduledTests(group);\r
+\r
+    if(!query){\r
+      query = {\r
+        groupId: groupId['_id'],\r
+        $select: [\r
+          'startTime',\r
+          'endTime',\r
+          "historicTestDefinition._id",\r
+          "historicTestDefinition.testName",\r
+          "historicTestInstance._id",\r
+          "historicTestInstance.testInstanceName",\r
+          "testHeadResults.startTime",\r
+          "testHeadResults.endTime",\r
+          "testHeadResults.testHeadName",\r
+          "testHeadResults.testHeadId",\r
+          "testHeadResults.testHeadGroupId",\r
+          "testHeadResults.statusCode",\r
+          'testResult'\r
+        ],\r
+        $limit: -1,\r
+        $sort: {\r
+          startTime: 1\r
+        },\r
+        startTime: {\r
+          $gte: this.filters.startDate,\r
+          $lte: this.filters.endDate\r
+        }\r
+      }\r
+    }\r
+\r
+    //Query test Executions\r
+    await new Promise((resolve, reject) => {\r
+      this.testExecution.find(query).subscribe(result => {\r
+\r
+        //reset arrays\r
+        this.testDefinitionData = {\r
+          //Executions Array thats made of objects with date, tdName, result\r
+          "Executions": [],\r
+          "Results": [],\r
+        }\r
+        this.testInstanceData = {\r
+          "Executions": [],\r
+          "Individual_Exec": []\r
+        };\r
+\r
+        this.executionList = result as Array<any>;\r
+        let currentData = this.testDefinitionData.Executions;\r
+\r
+        \r
+\r
+        //iterate through the results and populate the appropriate arrays. \r
+        for (let index in this.executionList) {\r
+\r
+          let newItem = this.executionList[index];\r
+\r
+          //get default line chart Data\r
+          this.populateLineChartData(newItem, currentData);\r
+\r
+          //get pie chart data. \r
+          this.populatePieChartData(newItem);\r
+\r
+          //Get BarChart Data\r
+          //this.populateBarChartData(newItem);\r
+\r
+          //get multi line chart data\r
+          //this.populateMultiLineChartData(newItem);\r
+        }\r
+\r
+        //sort the two arrays descending.\r
+        this.testInstanceData.Executions.sort((a, b) => b.Count - a.Count);\r
+        this.testInstanceData.Individual_Exec.sort((a, b) => b.total - a.total);\r
+        resolve();\r
+      }, err => {\r
+        reject(err);\r
+      });\r
+    }).then(res => {\r
+\r
+      // this.tdFilters = {\r
+      //   startDate: moment().subtract(2, 'weeks').toDate(),\r
+      //   endDate: moment().toDate(),\r
+      //   selected: [],\r
+      // };\r
+      // this.tiFilters = {\r
+      //   startDate: moment().subtract(2, 'weeks').toDate(),\r
+      //   endDate: moment().toDate(),\r
+      //   selectedTDs: [],\r
+      //   selectedTIs: [],\r
+      //   multiLineLimit: 5,\r
+      // }\r
+      this.finishedDefaultData.next(res);\r
+    })\r
+  }\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.pug
new file mode 100644 (file)
index 0000000..b44afa2
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#chart, [style.height]="height")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..70410d8
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestDefinitionExecutionsBarChartComponent } from './test-definition-executions-bar-chart.component';\r
+\r
+describe('TestDefinitionExecutionsBarChartComponent', () => {\r
+  let component: TestDefinitionExecutionsBarChartComponent;\r
+  let fixture: ComponentFixture<TestDefinitionExecutionsBarChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestDefinitionExecutionsBarChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestDefinitionExecutionsBarChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.ts
new file mode 100644 (file)
index 0000000..866de65
--- /dev/null
@@ -0,0 +1,198 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ElementRef, Input, OnDestroy } from '@angular/core';\r
+import { StatsService } from '../stats.service';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import { e } from '@angular/core/src/render3';\r
+import * as moment from 'moment';\r
+import { Router } from '@angular/router';\r
+import { Subscription } from 'rxjs';\r
+\r
+@Component({\r
+  selector: 'app-test-definition-executions-bar-chart',\r
+  templateUrl: './test-definition-executions-bar-chart.component.pug',\r
+  styleUrls: ['./test-definition-executions-bar-chart.component.scss']\r
+})\r
+export class TestDefinitionExecutionsBarChartComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  @ViewChild('chart') chartElement: ElementRef;\r
+  @Input() height: string;\r
+\r
+  public chart: am4charts.XYChart;\r
+  public testInstanceData;\r
+  public loadingIndicator;\r
+\r
+  constructor(private stats: StatsService, private router: Router) {}\r
+\r
+\r
+  ngOnInit() {\r
+    this.renderChart();\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData()\r
+    }));\r
+\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.toDestroy.forEach(e => e.unsubscribe());\r
+    this.chart.dispose();\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+    if(!this.loadingIndicator){\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight= "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    }else{\r
+      this.loadingIndicator.show();\r
+    }\r
+\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  setChartData() {\r
+\r
+    let data = [];\r
+    this.stats.executionList.forEach((execution, i) => {\r
+      let index = data.findIndex(e => e.id === execution.historicTestDefinition._id);\r
+      let executionTime = moment(execution.endTime).diff(moment(execution.startTime), 'seconds');\r
+      if(index == -1){\r
+        data.push({\r
+          id: execution.historicTestDefinition._id,\r
+          name: execution.historicTestDefinition.testName,\r
+          testResult: execution.testResult,\r
+          executionTime: executionTime,\r
+          count: 1,\r
+          average: executionTime\r
+        })\r
+      }else{\r
+        data[index].count += 1;\r
+        data[index].executionTime += executionTime;\r
+        data[index].average = (data[index].executionTime / data[index].count);\r
+      }\r
+    });\r
+    data.sort((a, b) => b.count - a.count);\r
+    this.chart.data = data;\r
+    \r
+\r
+    // Displays the average time for each bar. \r
+    // If there is no time recorded for the Test Instance, display No Time Recorded.\r
+    let series = this.chart.series.values[0] as am4charts.ColumnSeries;\r
+    \r
+    series.columns.template.adapter.add("tooltipText", (text, target) => {\r
+      if (target.dataItem) {\r
+        if (this.chart.data[target.dataItem.index].average > 0) {\r
+          return this.chart.data[target.dataItem.index].count.toString() + " Executions \n Avg Time: " + this.chart.data[target.dataItem.index].average.toFixed(2).toString() + " seconds";\r
+        } else\r
+          return this.chart.data[target.dataItem.index].count.toString() + " Executions \n No Time Recorded";\r
+      }\r
+    });\r
+    series.columns.template.adapter.add("fill", (fill, target) => this.chart.colors.getIndex(target.dataItem.index));\r
+    \r
+    \r
+    series.columns.template.events.on("doublehit", (click) => {\r
+      this.router.navigate(['/test-definitions', click.target.dataItem.dataContext['id']]);\r
+    });\r
+    this.chart.appear();\r
+    this.hideLoadingIndicator();\r
+    \r
+  }\r
+\r
+  renderChart() {\r
+    this.chart = am4core.create(this.chartElement.nativeElement, am4charts.XYChart);\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+    this.showLoadingIndicator();\r
+\r
+    this.chart.responsive.enabled = true;\r
+\r
+    // Create axes\r
+    var categoryAxis = this.chart.yAxes.push(new am4charts.CategoryAxis());\r
+    categoryAxis.dataFields.category = "name";\r
+    categoryAxis.numberFormatter.numberFormat = "#";\r
+    categoryAxis.renderer.inversed = true;\r
+    categoryAxis.renderer.minGridDistance = 5;\r
+    categoryAxis.title.fontSize = "10px";\r
+\r
+    var valueAxis = this.chart.xAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.renderer.minWidth = 10;\r
+\r
+    // Create series\r
+    var series = this.chart.series.push(new am4charts.ColumnSeries());\r
+    series.dataFields.valueX = "count";\r
+    series.dataFields.categoryY = "name";\r
+    series.columns.template.tooltipText = " ";\r
+\r
+    let label = categoryAxis.renderer.labels.template;\r
+    label.truncate = true;\r
+    label.maxWidth = 130;\r
+    label.fontSize = 13;\r
+\r
+    //Scrollbar on the right. \r
+    let scrollBarY = new am4charts.XYChartScrollbar();\r
+    //scrollBarY.series.push(series);\r
+    this.chart.scrollbarY = scrollBarY;\r
+    this.chart.scrollbarY.contentHeight = 100;\r
+    this.chart.scrollbarY.minWidth = 20;\r
+    this.chart.scrollbarY.thumb.minWidth = 20;\r
+\r
+    //set initial Scrollbar Zoom to the Top 6 Instances. \r
+    this.chart.events.on("appeared", () => {\r
+      \r
+      categoryAxis.zoomToIndexes(0, 6, false, true);\r
+    });\r
+   }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.pug
new file mode 100644 (file)
index 0000000..b44afa2
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#chart, [style.height]="height")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..351d650
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestHeadExecutionBarChartComponent } from './test-head-execution-bar-chart.component';\r
+\r
+describe('TestHeadExecutionBarChartComponent', () => {\r
+  let component: TestHeadExecutionBarChartComponent;\r
+  let fixture: ComponentFixture<TestHeadExecutionBarChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestHeadExecutionBarChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestHeadExecutionBarChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.ts
new file mode 100644 (file)
index 0000000..9b17262
--- /dev/null
@@ -0,0 +1,236 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ElementRef, Input, OnDestroy } from '@angular/core';\r
+import * as moment from 'moment';\r
+import { Subscription } from 'rxjs';\r
+import { StatsService } from '../stats.service';\r
+import { Router } from '@angular/router';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+@Component({\r
+  selector: 'app-test-head-execution-bar-chart',\r
+  templateUrl: './test-head-execution-bar-chart.component.pug',\r
+  styleUrls: ['./test-head-execution-bar-chart.component.scss']\r
+})\r
+export class TestHeadExecutionBarChartComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  @ViewChild('chart') chartElement: ElementRef;\r
+  @Input() height: string;\r
+\r
+  public chart: am4charts.XYChart;\r
+  public testInstanceData;\r
+  public loadingIndicator;\r
+\r
+  constructor(private stats: StatsService, private router: Router) {}\r
+\r
+\r
+  ngOnInit() {\r
+    \r
+    this.renderChart();\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData()\r
+    }));\r
+\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.toDestroy.forEach(e => e.unsubscribe());\r
+    this.chart.dispose();\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+    if(!this.loadingIndicator){\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight= "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    }else{\r
+      this.loadingIndicator.show();\r
+    }\r
+\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  incrementStatus(data, code){\r
+    \r
+    if(code >= 200 && code < 300){\r
+      data["200"]++;\r
+    }else if(code >= 300 && code < 400){\r
+      data["300"]++;\r
+    }else if(code >= 400 && code < 500){\r
+      data["400"]++;\r
+    }else if(code >= 500 && code < 600){\r
+      data["500"]++;\r
+    }else{\r
+      \r
+      data["other"]++;\r
+    }\r
+  }\r
+\r
+  setChartData() {\r
+\r
+    let data = [];\r
+    this.stats.executionList.forEach((execution, i) => {\r
+      execution.testHeadResults.forEach((result, val) => {\r
+        let index = data.findIndex(e => e.id === result.testHeadId);\r
+        let executionTime = moment(result.endTime).diff(moment(result.startTime), 'seconds');\r
+        if(index == -1){\r
+          let toPush = {\r
+            id: result.testHeadId,\r
+            name: result.testHeadName,\r
+            executionTime: executionTime,\r
+            count: 1,\r
+            average: executionTime,\r
+            "200": 0,\r
+            "300": 0,\r
+            "400": 0,\r
+            "500": 0,\r
+            "other": 0\r
+          }\r
+          this.incrementStatus(toPush, result.statusCode);\r
+          data.push(toPush);\r
+        }else{\r
+          this.incrementStatus(data[index], result.statusCode);\r
+          data[index].count += 1;\r
+          data[index].executionTime += executionTime;\r
+          data[index].average = (data[index].executionTime / data[index].count);\r
+        }\r
+      });\r
+    });\r
+    data.sort((a, b) => b.count - a.count);\r
+    this.chart.data = data;\r
+\r
+    // Displays the average time for each bar. \r
+    // If there is no time recorded for the Test Instance, display No Time Recorded.\r
+    let series = this.chart.series.values as Array<am4charts.ColumnSeries>;\r
+    \r
+    // series.columns.template.adapter.add("tooltipText", (text, target) => {\r
+    //   if (target.dataItem) {\r
+    //     if (this.chart.data[target.dataItem.index].average > 0) {\r
+    //       return this.chart.data[target.dataItem.index].count.toString() + " Executions \n Avg Time: " + this.chart.data[target.dataItem.index].average.toFixed(2).toString() + " seconds";\r
+    //     } else\r
+    //       return this.chart.data[target.dataItem.index].count.toString() + " Executions \n No Time Recorded";\r
+    //   }\r
+    // });\r
+\r
+    series.forEach(elem => {\r
+      // elem.columns.template.adapter.add("fill", (fill, target) => this.chart.colors.getIndex(target.dataItem.index));\r
+    \r
+    \r
+      elem.columns.template.events.on("doublehit", (click) => {\r
+        this.router.navigate(['/test-heads', click.target.dataItem.dataContext['id']]);\r
+      });\r
+    })\r
+    \r
+    this.chart.appear();\r
+    this.hideLoadingIndicator();\r
+  }\r
+\r
+  renderChart() {\r
+    this.chart = am4core.create(this.chartElement.nativeElement, am4charts.XYChart);\r
+\r
+    this.showLoadingIndicator();\r
+\r
+    this.chart.responsive.enabled = true;\r
+\r
+    // Create axes\r
+    var categoryAxis = this.chart.yAxes.push(new am4charts.CategoryAxis());\r
+    categoryAxis.dataFields.category = "name";\r
+    categoryAxis.numberFormatter.numberFormat = "#";\r
+    categoryAxis.renderer.inversed = true;\r
+    categoryAxis.renderer.minGridDistance = 5;\r
+    categoryAxis.title.fontSize = "10px";\r
+\r
+    var valueAxis = this.chart.xAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.renderer.minWidth = 10;\r
+\r
+    this.createSeries("200", "200s")\r
+    this.createSeries("300", "300s")\r
+    this.createSeries("400", "400s")\r
+    this.createSeries("500", "500s")\r
+    this.createSeries("other", "Other")\r
+\r
+    this.chart.legend = new am4charts.Legend();\r
+    this.chart.legend.scale = .7;\r
+    this.chart.legend.width = am4core.percent(150);\r
+\r
+    let label = categoryAxis.renderer.labels.template;\r
+    label.truncate = true;\r
+    label.maxWidth = 130;\r
+    label.fontSize = 13;\r
+\r
+    //Scrollbar on the right. \r
+    let scrollBarY = new am4charts.XYChartScrollbar();\r
+    //scrollBarY.series.push(series);\r
+    this.chart.scrollbarY = scrollBarY;\r
+    this.chart.scrollbarY.contentHeight = 100;\r
+    this.chart.scrollbarY.minWidth = 20;\r
+    this.chart.scrollbarY.thumb.minWidth = 20;\r
+\r
+    //set initial Scrollbar Zoom to the Top 6 Instances. \r
+    this.chart.events.on("appeared", () => {\r
+      categoryAxis.zoomToIndexes(0, 6, false, true);\r
+    });\r
+   }\r
+\r
+  createSeries(field, name){\r
+    // Create series\r
+    var series = this.chart.series.push(new am4charts.ColumnSeries());\r
+    series.dataFields.valueX = field;\r
+    series.dataFields.categoryY = "name";\r
+    series.stacked = true;\r
+    series.name = name;\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.pug b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.pug
new file mode 100644 (file)
index 0000000..b44afa2
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#chart, [style.height]="height")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.scss b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..af1de53
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestHeadExecutionsLineChartComponent } from './test-head-executions-line-chart.component';\r
+\r
+describe('TestHeadExecutionsLineChartComponent', () => {\r
+  let component: TestHeadExecutionsLineChartComponent;\r
+  let fixture: ComponentFixture<TestHeadExecutionsLineChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestHeadExecutionsLineChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestHeadExecutionsLineChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.ts
new file mode 100644 (file)
index 0000000..a214c87
--- /dev/null
@@ -0,0 +1,219 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, ElementRef, OnDestroy } from '@angular/core';\r
+import { StatsService } from '../stats.service';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import { _ } from 'ag-grid-community';\r
+import * as moment from 'moment';\r
+import { Subscription } from 'rxjs';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+\r
+@Component({\r
+  selector: 'app-test-head-executions-line-chart',\r
+  templateUrl: './test-head-executions-line-chart.component.pug',\r
+  styleUrls: ['./test-head-executions-line-chart.component.scss']\r
+})\r
+export class TestHeadExecutionsLineChartComponent implements OnInit {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  @ViewChild('chart') chartElement: ElementRef;\r
+  @Input() height: string;\r
+\r
+  //public testDefinitionName = "Hello";\r
+  private chart: am4charts.XYChart;\r
+  private loadingIndicator;\r
+\r
+  constructor(private stats: StatsService, private _groups: GroupService) {\r
+  }\r
+\r
+  ngOnInit() {\r
+    this.renderChart();\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    //destory chart\r
+    this.toDestroy.forEach(e => e.unsubscribe());\r
+    this.chart.dispose();\r
+  }\r
+\r
+  //Sets count to 0 for any dates that dont have data\r
+  setupPoints(rawData) {\r
+    let formattedData = [];\r
+    let dayRange = moment(this.stats.filters.endDate).add(1, 'days').diff(moment(this.stats.filters.startDate), 'days');\r
+    for (let i = 0; i < dayRange; i++) {\r
+      //find date in raw data\r
+      let d = rawData.find(e => moment(e.date).isSame(moment(this.stats.filters.startDate).add(i, 'days'), 'day'));\r
+      let myTestHeads = 0;\r
+      let otherTestHeads = 0;\r
+      if (d) {\r
+        myTestHeads = d.myTestHeads || 0;\r
+        otherTestHeads = d.otherTestHeads || 0;\r
+      }\r
+      formattedData.push({\r
+        date: moment(this.stats.filters.startDate).startOf('day').add(i, 'days').toDate(),\r
+        myTestHeads: myTestHeads,\r
+        otherTestHeads: otherTestHeads\r
+      })\r
+    }\r
+\r
+    return formattedData;\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+\r
+    //this.height = "380px";\r
+    if (!this.loadingIndicator) {\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight = "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    } else {\r
+      this.loadingIndicator.show();\r
+    }\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  async setChartData() {\r
+    let data = [];\r
+\r
+    this.stats.executionList.forEach((e, i) => {\r
+      if (e.testHeadResults && e.testHeadResults.length > 0) {\r
+\r
+        e.testHeadResults.forEach((result, index) => {\r
+\r
+          let isMyTestHead = result.testHeadGroupId == this._groups.getGroup()['_id'];\r
+\r
+\r
+\r
+          let dataIndex = data.findIndex(d => moment(d.date).isSame(result.startTime, 'day'));\r
+\r
+          if (dataIndex == -1) {\r
+            dataIndex = data.push({ date: moment(result.startTime).toDate() }) - 1;\r
+          }\r
+\r
+          if (isMyTestHead) {\r
+            if (data[dataIndex]['myTestHeads']) {\r
+              data[dataIndex]['myTestHeads'] += 1;\r
+            } else {\r
+              data[dataIndex]['myTestHeads'] = 1;\r
+            }\r
+          }else{\r
+            if (data[dataIndex]['otherTestHeads']) {\r
+              data[dataIndex]['otherTestHeads'] += 1;\r
+            } else {\r
+              data[dataIndex]['otherTestHeads'] = 1;\r
+            }\r
+          }\r
+\r
+        })\r
+      }\r
+    });\r
+\r
+    \r
+\r
+    this.chart.data = this.setupPoints(data);\r
+\r
+    this.hideLoadingIndicator();\r
+  }\r
+\r
+  renderChart() {\r
+\r
+    if (this.chart) {\r
+      this.chart.dispose();\r
+    }\r
+    this.chart = am4core.create(this.chartElement.nativeElement, am4charts.XYChart);\r
+    this.chart.preloader.disabled = true;\r
+    this.showLoadingIndicator();\r
+\r
+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
+    dateAxis.fontSize = "10px";\r
+\r
+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.title.text = "Executions";\r
+    valueAxis.title.fontSize = "10px";\r
+\r
+    let series = this.chart.series.push(new am4charts.LineSeries());\r
+    series.name = "My Group's VTHs"\r
+    series.dataFields.dateX = "date";\r
+    series.dataFields.valueY = "myTestHeads";\r
+    series.strokeWidth = 3;\r
+\r
+    series.fillOpacity = .5;  \r
+    // series.tensionX = 0.8;\r
+    series.sequencedInterpolation = false;\r
+    series.tooltipText = "{valueY.value}";\r
+\r
+    let seriesOthers = this.chart.series.push(new am4charts.LineSeries());\r
+    seriesOthers.name = "Other VTHs";\r
+    seriesOthers.dataFields.dateX = "date";\r
+    seriesOthers.dataFields.valueY = "otherTestHeads";\r
+    seriesOthers.strokeWidth = 3;\r
+    seriesOthers.tooltipText = "{valueY.value}";\r
+\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+\r
+    this.chart.responsive.enabled = true;\r
+\r
+    this.chart.legend = new am4charts.Legend();\r
+    this.chart.legend.labels.template.text = "[bold {color}]{name}";\r
+    this.chart.legend.scale = .8;\r
+  }\r
+\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel-routing.module.ts b/otf-frontend/client/src/app/layout/control-panel/control-panel-routing.module.ts
new file mode 100644 (file)
index 0000000..29b38ef
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { ControlPanelComponent } from './control-panel.component';\r
+\r
+const routes: Routes = [\r
+  { path: '', component: ControlPanelComponent }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class ControlPanelRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel.component.pug b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.pug
new file mode 100644 (file)
index 0000000..56b688f
--- /dev/null
@@ -0,0 +1,270 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition], style="margin-top: -15px")\r
+    .row.fullWidth.mb-4(style="position:relative")\r
+        div.pull-tab\r
+            div.arrow\r
+        mat-card#canvas-card(style="width: 100%; resize:vertical; overflow: hidden")\r
+            mat-card-content#canvas(style="height: 100%")\r
+                div(style="position: absolute; bottom: 5px; left: 10px")\r
+                    div.small.text-muted Test Definition: {{ testResult?.historicTestDefinition?.testName }}\r
+                    div.small.text-muted Version: {{ testResult?.historicTestDefinition?.bpmnInstances[0]?.version }}\r
+                div(style="position: absolute; bottom: 5px; right: 25px")\r
+                    div.small.text-muted(*ngIf="processState")\r
+                        | Status: {{ processState }}\r
+    .row.mb-4\r
+        .col\r
+            .pull-left\r
+                h3 {{ testResult?.historicTestInstance?.testInstanceName }} Execution Log\r
+                div {{ testResult?.historicTestInstance?.testInstanceDescription }}\r
+            button.mr-2.pull-right(color="accent", mat-raised-button, (click)="refreshAllData()") Refresh\r
+                i.fa.fa-2x.fa-fw.fa-refresh.fast-spin(*ngIf="spin")\r
+            button.mr-2.pull-right(*ngIf="processState && processState == 'Running'", color="warn", mat-raised-button, (click)="cancelExecution()") Cancel Execution\r
+    .row.mb-4\r
+        .col-12\r
+            mat-tab-group([selectedIndex]="0", dinamicHeight, color="accent", backgroundColor="primary")\r
+                mat-tab(label="Overview")\r
+                    .col-12.mt-2\r
+                        .row.mb-4(style="text-align:center")\r
+                            .col\r
+                                b Test Result:\r
+                                div {{ testResult?.testResult }}\r
+                            .col\r
+                                b Start Time:\r
+                                div {{ testResult?.startTime }}\r
+                            .col\r
+                                b End Time:\r
+                                div {{ testResult?.endTime }}\r
+                            .col\r
+                                b Total Time:\r
+                                div {{ testResult?.totalTime }}\r
+                            .col\r
+                                b Date Executed:\r
+                                div {{ testResult?.date }}\r
+                            .col\r
+                                b Executed By:\r
+                                div {{ testResult?.historicEmail }}\r
+                        hr\r
+                        .row.mb-4(*ngIf="testResult?.testResultMessage")\r
+                            .col-12\r
+                                h5 Test Result Message\r
+                                | {{ testResult?.testResultMessage }}\r
+                        .row\r
+                            .col-12\r
+                                h5 Test Details\r
+                                //- table(datatable, [dtOptions]="dtOptions", class="row-border hover")\r
+                                //-     thead\r
+                                //-         tr\r
+                                //-             th(*ngFor="let column of columns") {{ column.title }}\r
+                                //-     tbody\r
+                                //-         tr(*ngFor="let key of data")\r
+                                //-             td(*ngFor="let column of columns") {{ key[column.data] }}\r
+                                ngx-json-viewer(*ngIf="testResult && testResult.testDetails != {}", [json]="testResult.testDetails", style="font-size: 1.3em")\r
+                                //- div(*ngIf="testResult && testResult.testDetails != {}", [innerHTML]="json2html(testResult.testDetails)")\r
+                                //- div(*ngIf="testResult") {{ json2html(testResult.testDetails) != '' ? '' : 'No test details were set during execution.' }}\r
+\r
+                mat-tab(*ngIf="testResult?.testInstanceResults.length > 0", label="Test Instance Results")\r
+                    .col-12.mt-2\r
+                        .row\r
+                            .col-3\r
+                                h5 Test Instances\r
+                                mat-list\r
+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(instanceDataSource); last as last; first as first", (click)="selectTestInstance(key)")\r
+                                        h5 {{ instanceDataSource[key][0].historicTestInstance.testInstanceName }}\r
+                                        mat-icon([hidden]="selectedTestInstance != key") keyboard_arrow_left\r
+                                        mat-divider([inset]="true", *ngIf="!last")\r
+                                mat-divider(vertical)\r
+\r
+                            .col-9\r
+                                mat-accordion([multi]="true")\r
+                                    mat-expansion-panel(*ngFor="let element of instanceDataSource[selectedTestInstance]; let i = index;")\r
+                                        mat-expansion-panel-header()\r
+                                            mat-panel-title {{ '#' + (i + 1)}}\r
+                                            mat-panel-description\r
+                                                | {{ element.testResult }}\r
+                                        .col-12\r
+                                            .row(style="text-align:center")\r
+                                                .col\r
+                                                    b Test Result:\r
+                                                    div {{ element.testResult }}\r
+                                                .col\r
+                                                    b Start Time:\r
+                                                    div {{ element.startTime }}\r
+                                                .col\r
+                                                    b End Time:\r
+                                                    div {{ element.endTime }}\r
+                                                .col\r
+                                                    b Total Time:\r
+                                                    div {{ element.totalTime }}\r
+                                                .col\r
+                                                    b Date Executed:\r
+                                                    div {{ element.date }}\r
+                                            hr\r
+                                            div(style="float:right")\r
+                                                button(mat-raised-button, color="primary", [routerLink]="['/control-panel']", [queryParams]="{id: element._id}") Full Execution\r
+                                            .row.mb-4(*ngIf="element.testResultMessage")\r
+                                                .col-12\r
+                                                    h5 Test Result Message\r
+                                                    | {{ element.testResultMessage }}\r
+                                            .row\r
+                                                .col-12\r
+                                                    h5 Test Details\r
+                                                    ngx-json-viewer(*ngIf="element && element.testDetails != {}", [json]="element.testDetails")\r
+                                                    //- div(*ngIf="element && element.testDetails != {}", [innerHTML]="json2html(element.testDetails)")\r
+                                                    //- div(*ngIf="testResult") {{ json2html(element.testDetails) != '' ? '' : 'No test details were set during execution.' }}\r
+\r
+                mat-tab(*ngIf="testResult?.testHeadResults.length > 0", label="Test Head Results")\r
+                    .col-12.mt-2\r
+                        .row\r
+                            .col-3\r
+                                h5 Test Heads\r
+                                mat-list\r
+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(dataSource); last as last; first as first", (click)="selectTestHead(key)")\r
+                                        div\r
+                                            h5 {{ dataSource[key][0].testHeadName }}\r
+                                            div.small(style="margin-top:-11px;") {{ dataSource[key][0].bpmnVthTaskId }}\r
+                                        mat-icon([hidden]="selectedTestHead != key") keyboard_arrow_left\r
+                                        mat-divider([inset]="true", *ngIf="!last")\r
+                                mat-divider(vertical)\r
+\r
+                            .col-9\r
+                                div(*ngFor="let testHead of testHeads")\r
+                                    mat-accordion([multi]="true", *ngIf="selectedTestHead == testHead.testHeadId + testHead.bpmnVthTaskId")\r
+                                        mat-expansion-panel(*ngFor="let element of dataSource[testHead.testHeadId + testHead.bpmnVthTaskId]; let i = index;")\r
+                                            mat-expansion-panel-header()\r
+                                                mat-panel-title {{ '#' + (i + 1)}}\r
+                                                mat-panel-description\r
+                                                    | {{ element.totalTime }}\r
+                                            ngx-json-viewer([json]="element.testHeadRequestResponse")\r
+                                            app-robot-report(*ngIf="element.testHeadResponse", [response]="element.testHeadResponse")\r
+\r
+                    //- (*ngFor="let testHead of testHeads")\r
+                    //-     mat-card\r
+                    //-         mat-card-header.COMPLETED-dash\r
+                    //-             mat-card-title.pull-left {{testHead.testHeadName}} Results\r
+                    //-             .pull-right {{testHead.bpmnVthTaskId}}\r
+                    //-         mat-card-content\r
+                    //-             table(mat-table, multiTemplateDataRows, [dataSource]="dataSource[testHead.testHeadId + testHead.bpmnVthTaskId]")\r
+\r
+                    //-                 ng-container(matColumnDef="startTime")\r
+                    //-                     th(mat-header-cell, *matHeaderCellDef) Start Time\r
+                    //-                     td(mat-cell, *matCellDef="let element") {{ element.startTime}}\r
+\r
+                    //-                 ng-container(matColumnDef="endTime")\r
+                    //-                     th(mat-header-cell, *matHeaderCellDef) End Time\r
+                    //-                     td(mat-cell, *matCellDef="let element") {{ element.endTime }}\r
+\r
+                    //-                 ng-container(matColumnDef="totalTime")\r
+                    //-                     th(mat-header-cell, *matHeaderCellDef) Total Time\r
+                    //-                     td(mat-cell, *matCellDef="let element") {{ element.totalTime }}\r
+\r
+                    //-                 ng-container(matColumnDef="expandedDetail")\r
+                    //-                     td(mat-cell, style="padding:0px", *matCellDef="let element; let i = dataIndex", [attr.colspan]="displayedColumns.length")\r
+                    //-                         div([@detailExpand]="(testHead.testHeadId + testHead.bpmnVthTaskId + i) == expandedElement ? 'expanded' : 'collapsed'")\r
+                    //-                             codemirror([config]="codeConfig", [value]='element.testHeadResponse', name="testHeadResult")\r
+                    //-                             app-robot-report(*ngIf="element.testHeadResponse", [response]="element.testHeadResponse")\r
+\r
+                    //-                 tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+                    //-                 tr.example-element-row(mat-row, *matRowDef="let element; columns: displayedColumns; let i = dataIndex", [class.example-expanded-row]="expandedElement === element", (click)="expand(testHead.testHeadId + testHead.bpmnVthTaskId + i)")\r
+                    //-                 tr.example-detail-row(mat-row, *matRowDef="let row; columns: ['expandedDetail']")\r
+\r
+                mat-tab(label="Task Log", *ngIf="taskLog != ''")\r
+                    .col-12.mt-2\r
+                        h5 Task Logs\r
+                        div {{ taskLog }}\r
+\r
+                mat-tab(label="Test Parameters", *ngIf="testResult?.historicTestInstance")\r
+                    .col-12.mt-2\r
+                        h5 Test Data\r
+                        ngx-json-viewer([json]="testResult.historicTestInstance.testData", [expanded]="false", style="font-size: 1.3em")\r
+                        //- div([innerHTML]="json2html(testResult.historicTestInstance.testData, 1)")\r
+                        //- | {{ testResult.historicTestInstance.testData ? '' : 'No test data set.'}}\r
+                        h5.mt-1 Test Head Input\r
+                        ngx-json-viewer([json]="testResult.historicTestInstance.vthInput", [expanded]="false", style="font-size: 1.3em")\r
+                        //- div([innerHTML]="json2html(testResult.historicTestInstance.vthInput, 1)")\r
+                        //- | {{ testResult.historicTestInstance.vthInput ? '' : 'No test head input set.'}}\r
+\r
+\r
+                mat-tab(*ngIf="executionJobLogDataSource != undefined", label="Execution Job Log")\r
+                    .col-12.mt-2\r
+                        .row\r
+                            .col-3\r
+                                h5 Execution Job Log\r
+                                mat-list\r
+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(executionJobLogDataSource); last as last; first as first", (click)="selectExecutionJobLog(key)")\r
+                                        h5 {{ executionJobLogDataSource[key][0].activityId }}\r
+                                        mat-icon([hidden]="selectedExecutionJobLog != key") keyboard_arrow_left\r
+                                        mat-divider([inset]="true", *ngIf="!last")\r
+                                mat-divider(vertical)\r
+\r
+                            .col-9\r
+                                mat-accordion([multi]="true")\r
+                                    mat-expansion-panel(*ngFor="let element of executionJobLogDataSource[selectedExecutionJobLog]; let i = index;")\r
+                                        mat-expansion-panel-header()\r
+                                            mat-panel-title {{ '#' + (i + 1)}}\r
+                                            mat-panel-description\r
+                                                | {{ element.id }}\r
+                                        ngx-json-viewer([json]="element")\r
+                                        //- div([innerHTML]="json2html(element)")\r
+                                        //-     | {{ element ? '' : 'No job log' }}\r
+\r
+                mat-tab(*ngIf="executionExternalTaskLogDataSource != undefined", label="Execution External Task Log")\r
+                    .col-12.mt-2\r
+                        .row\r
+                            .col-3\r
+                                h5 Execution External Task Log\r
+                                mat-list\r
+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(executionExternalTaskLogDataSource); last as last; first as first", (click)="selectExecutionExternalTaskLog(key)")\r
+                                        h5 {{ executionExternalTaskLogDataSource[key][0].activityId }}\r
+                                        mat-icon([hidden]="selectedExecutionExternalTaskLog != key") keyboard_arrow_left\r
+                                        mat-divider([inset]="true", *ngIf="!last")\r
+                                mat-divider(vertical)\r
+\r
+                            .col-9\r
+                                mat-accordion([multi]="true")\r
+                                    mat-expansion-panel(*ngFor="let element of executionExternalTaskLogDataSource[selectedExecutionExternalTaskLog]; let i = index;")\r
+                                        mat-expansion-panel-header()\r
+                                            mat-panel-title {{ '#' + (i + 1)}}\r
+                                            mat-panel-description\r
+                                                | {{ element.id }}\r
+                                        ngx-json-viewer([json]="element")\r
+                                        //- div([innerHTML]="json2html(element)")\r
+                                        //-     | {{ element ? '' : 'No external task log' }}\r
+\r
+\r
+                mat-tab(*ngIf="executionVariablesDataSource != undefined", label="Execution Variables")\r
+                    .col-12.mt-2\r
+                        .row\r
+                            .col-3\r
+                                h5 Execution Variables\r
+                                mat-list\r
+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(executionVariablesDataSource); last as last; first as first", (click)="selectExecutionVariable(key)")\r
+                                        h5 {{ executionVariablesDataSource[key][0].variableName }}\r
+                                        mat-icon([hidden]="selectedExecutionVariable != key") keyboard_arrow_left\r
+                                        mat-divider([inset]="true", *ngIf="!last")\r
+                                mat-divider(vertical)\r
+\r
+                            .col-9\r
+                                mat-accordion([multi]="true")\r
+                                    mat-expansion-panel(*ngFor="let element of executionVariablesDataSource[selectedExecutionVariable]; let i = index;")\r
+                                        mat-expansion-panel-header()\r
+                                            mat-panel-title {{ '#' + (i + 1)}}\r
+                                            mat-panel-description\r
+                                                | {{ element.id }}\r
+                                        ngx-json-viewer([json]="element")\r
+                                        //- div([innerHTML]="json2html(element)")\r
+                                        //-     | {{ element ? '' : 'No variables' }}\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel.component.scss b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.scss
new file mode 100644 (file)
index 0000000..eae8b37
--- /dev/null
@@ -0,0 +1,132 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.COMPLETED-dash {\r
+    background-color: #0d47a1;\r
+    color: white;\r
+}\r
+\r
+.SUCCESS-dash {\r
+    background-color: #199700;\r
+    color: white;\r
+}\r
+\r
+.FAILURE-dash {\r
+    background-color: #dd2c00 !important;\r
+    color: white;\r
+}\r
+\r
+.fast-spin {\r
+  -webkit-animation: fa-spin 1s infinite linear;\r
+  animation: fa-spin 1s infinite linear;\r
+}\r
+\r
+.STOPPED-dash {\r
+    background-color: #ff9100;\r
+    color: white;\r
+}\r
+\r
+.UNAUTHORIZED-dash {\r
+    background-color: #000000;\r
+    color: white;\r
+}\r
+\r
+.UNKNOWN-dash {\r
+    background-color: White;\r
+}\r
+\r
+table {\r
+    width: 100%;\r
+    table-layout:fixed; \r
+  }\r
+  \r
+  tr.example-detail-row {\r
+    height: 0;\r
+  }\r
+  \r
+  tr.example-element-row:not(.example-expanded-row):hover {\r
+    background: #f5f5f5;\r
+    cursor: pointer;\r
+  }\r
+  \r
+  tr.example-element-row:not(.example-expanded-row):active {\r
+    background: #efefef;\r
+    cursor: pointer;\r
+  }\r
+  \r
+  .example-element-row td {\r
+    border-bottom-width: 0;\r
+  }\r
+  \r
+  .example-element-detail {\r
+    //overflow: hidden;\r
+    //display: flex;\r
+  }\r
+  \r
+  .example-element-diagram {\r
+    min-width: 80px;\r
+    border: 2px solid black;\r
+    padding: 8px;\r
+    font-weight: lighter;\r
+    margin: 8px 0;\r
+    height: 104px;\r
+  }\r
+  \r
+  .example-element-symbol {\r
+    font-weight: bold;\r
+    font-size: 40px;\r
+    line-height: normal;\r
+  }\r
+  \r
+  .example-element-description {\r
+    padding: 16px;\r
+  }\r
+  \r
+  .example-element-description-attribution {\r
+    opacity: 0.5;\r
+  }\r
+\r
+\r
+.active-testHead {\r
+  background-color: #f8f9fa;\r
+}\r
+\r
+.pull-tab {\r
+  height: 0px;\r
+  width: 0px;\r
+  border-top: 20px solid #007bff;\r
+  border-left: 20px solid transparent;\r
+  border-right: 20px solid transparent;\r
+  -webkit-transform: rotate(-45deg);\r
+  position: absolute;\r
+  bottom: 0px;\r
+  right: -15px;\r
+  pointer-events: none;\r
+  z-index: 1;\r
+  margin-bottom: -4px;\r
+}\r
+\r
+.arrow {\r
+  border: solid black;\r
+  border-width: 0 3px 3px 0;\r
+  display: inline-block;\r
+  padding: 3px;\r
+  position: absolute;\r
+  transform: rotate(45deg);\r
+  -webkit-transform: rotate(45deg);\r
+  right: -2px;\r
+  bottom: 11px;\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel.component.spec.ts b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.spec.ts
new file mode 100644 (file)
index 0000000..abaf3dd
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ControlPanelComponent } from './control-panel.component';\r
+\r
+describe('ControlPanelComponent', () => {\r
+  let component: ControlPanelComponent;\r
+  let fixture: ComponentFixture<ControlPanelComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ControlPanelComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ControlPanelComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel.component.ts b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.ts
new file mode 100644 (file)
index 0000000..f9144be
--- /dev/null
@@ -0,0 +1,564 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';\r
+import { routerTransition } from 'app/router.animations';\r
+import { ActivatedRoute } from '@angular/router';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import BpmnJS from 'bpmn-js/lib/NavigatedViewer';\r
+import beautify from 'json-beautify';\r
+import { trigger, state, style, transition, animate } from '@angular/animations';\r
+import { interval } from 'rxjs';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { Buffer } from 'buffer';\r
+import 'codemirror/mode/javascript/javascript.js';\r
+import { toInteger } from '@ng-bootstrap/ng-bootstrap/util/util';\r
+import { RequiredValidator } from '@angular/forms';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { ExecuteService } from 'app/shared/services/execute.service';\r
+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';\r
+import { Bpmn } from 'app/shared/models/bpmn.model';\r
+\r
+//import 'datatables.net';\r
+\r
+@Component({\r
+  selector: 'app-control-panel',\r
+  templateUrl: './control-panel.component.pug',\r
+  styleUrls: ['./control-panel.component.scss'],\r
+  animations: [routerTransition(),\r
+  trigger('detailExpand', [\r
+    state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),\r
+    state('expanded', style({ height: '*' })),\r
+    transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),\r
+  ])\r
+  ]\r
+})\r
+export class ControlPanelComponent implements OnInit, OnDestroy {\r
+\r
+  @ViewChild('canvas-card') canvas: ElementRef;\r
+\r
+  public params;\r
+  public displayedColumns = ['startTime', 'endTime', 'totalTime'];\r
+  public dataSource = {};\r
+  public instanceDataSource = {};\r
+  public data = {};\r
+  public testHeads = [];\r
+  public testResult;\r
+  public expandedElement;\r
+  public selectedTestHead;\r
+  public selectedTestInstance;\r
+  public objectKeys = Object.keys;\r
+  private pullData = true;\r
+  public showFireworks = false;\r
+  public refreshData = false;\r
+  public spin = false;\r
+  public lastVTHResultsLength = 0;\r
+  // Create an Observable that will publish a value on an interval\r
+  public refreshCounter = interval(5000);\r
+\r
+  public isResizing = false;\r
+  public lastDownY;\r
+\r
+  public viewer: Bpmn;\r
+  public taskLog = '';\r
+\r
+  public executionJobLog = [];\r
+  public executionExternalTaskLog = [];\r
+  public executionVariables = [];\r
+\r
+  public executionJobLogDataSource;\r
+  public executionExternalTaskLogDataSource;\r
+  public executionVariablesDataSource;\r
+\r
+  public selectedExecutionJobLog;\r
+  public selectedExecutionExternalTaskLog;\r
+  public selectedExecutionVariable;\r
+\r
+\r
+  public codeConfig = {\r
+    mode: "application/json",\r
+    theme: "eclipse",\r
+    readonly: true,\r
+    lineNumbers: true\r
+  };\r
+\r
+  public taskLogConfig = {\r
+    mode: "Shell",\r
+    theme: "3024-night",\r
+    readonly: true\r
+  };\r
+\r
+  private processInstanceId;\r
+  public processState;\r
+\r
+\r
+  constructor(\r
+    private route: ActivatedRoute,\r
+    private executionService: ExecuteService,\r
+    private user: UserService,\r
+    private testExecution: TestExecutionService,\r
+    private fileTransfer: FileTransferService,\r
+    private bpmnFactory: BpmnFactoryService\r
+  ) { }\r
+\r
+  ngOnInit() {\r
+    this.route.queryParams.subscribe(params => {\r
+      this.params = params;\r
+      if(params.id){\r
+        this.refreshData = false;\r
+        this.populateData();\r
+\r
+        this.refreshCounter.subscribe(n => {\r
+          if (this.pullData){\r
+            this.populateData(n + 1);\r
+            this.updateFlowData();\r
+\r
+          }\r
+        });\r
+      }\r
+    });\r
+\r
+    $('#canvas-card').on('mousedown', (e) => {\r
+      this.isResizing = true;\r
+      this.lastDownY = $('#canvas-card').position().top + $('#canvas-card').outerHeight(true);\r
+    })\r
+\r
+    $(document).on('mousemove', (e) => {\r
+      if(!this.isResizing){\r
+        return;\r
+      }\r
+\r
+      var bottom = $('#canvas-card').position().top + $('#canvas-card').outerHeight(true);//$('#canvas-card').height() - (e.clientY - $('#canvas-card').offset().top);\r
+\r
+      if(bottom != this.lastDownY){\r
+        this.lastDownY = $('#canvas-card').position().top + $('#canvas-card').outerHeight(true);\r
+        this.onResize(null);\r
+      }\r
+\r
+    }).on('mouseup', () => {\r
+      this.isResizing = false;\r
+    })\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.pullData = false;\r
+  }\r
+\r
+  refreshAllData() {\r
+    this.spin = true;\r
+    this.refreshData = true;\r
+    this.populateData();\r
+    this.updateFlowData();\r
+\r
+  }\r
+\r
+  populateData(loopNum = 0) {\r
+    this.testExecution.get(this.params.id).subscribe(\r
+      data => {\r
+        console.log(data);\r
+        let result = JSON.parse(JSON.stringify(data));\r
+\r
+        this.processInstanceId = result['processInstanceId'];\r
+\r
+        this.calcTime(result);\r
+\r
+        if(result['testInstanceResults']){\r
+          this.instanceDataSource = {};\r
+          for(var val = 0; val < result['testInstanceResults'].length; val++){\r
+            var elem = result['testInstanceResults'][val];\r
+            this.calcTime(elem);\r
+            let exists = false;\r
+            Object.keys(this.instanceDataSource).forEach((e, val) => {\r
+                if(e == elem.historicTestInstance._id){\r
+                    exists = true;\r
+                    return;\r
+                  }\r
+            });\r
+\r
+            if(!exists){\r
+              this.instanceDataSource[elem.historicTestInstance._id] = [elem];\r
+            }\r
+            else{\r
+                var found = false;\r
+\r
+                this.instanceDataSource[elem.historicTestInstance._id].forEach( (value, index) => {\r
+                    if(this.instanceDataSource[elem.historicTestInstance._id][index]._id == elem._id){\r
+                        this.instanceDataSource[elem.historicTestInstance._id][index] = elem;\r
+                        found = true;\r
+                    }\r
+                })\r
+                if(!found){\r
+                    this.instanceDataSource[elem.historicTestInstance._id].push(elem);\r
+                }\r
+            }\r
+            if(val == 0){\r
+              this.selectTestInstance(elem.historicTestInstance._id);\r
+            }\r
+          };\r
+        }\r
+\r
+        if (result['testHeadResults']) {\r
+          for (var i = 0 + this.lastVTHResultsLength; i < result['testHeadResults'].length; i++) {\r
+\r
+            var exists = false;\r
+            this.testHeads.forEach(elem => {\r
+              if (elem.testHeadId == result['testHeadResults'][i].testHeadId && elem.bpmnVthTaskId == result['testHeadResults'][i].bpmnVthTaskId) {\r
+                exists = true;\r
+              }\r
+            });\r
+\r
+            if (!exists) {\r
+              this.testHeads.push(result['testHeadResults'][i]);\r
+              this.dataSource[result['testHeadResults'][i].testHeadId + result['testHeadResults'][i].bpmnVthTaskId] = [];\r
+            }\r
+\r
+            let sDate = new Date(result['testHeadResults'][i].startTime);\r
+            let eDate = new Date(result['testHeadResults'][i].endTime);\r
+            let tDate = (eDate.getTime() - sDate.getTime()) / 1000;\r
+\r
+            result['testHeadResults'][i].startTime = sDate.getHours() + ":" + sDate.getMinutes() + ":" + sDate.getSeconds(); // + " " + sDate.getMonth() + "/" + sDate.getDate() + "/" + sDate.getFullYear();\r
+            result['testHeadResults'][i].endTime = eDate.getHours() + ":" + eDate.getMinutes() + ":" + eDate.getSeconds(); // + " " + eDate.getMonth() + "/" + eDate.getDate() + "/" + eDate.getFullYear();\r
+            result['testHeadResults'][i].totalTime = tDate + " secs";\r
+            result['testHeadResults'][i].testHeadRequestResponse = {\r
+                "testHeadRequest": result['testHeadResults'][i].testHeadRequest,\r
+                "testHeadResponse": result['testHeadResults'][i].testHeadResponse,\r
+                "statusCode": result['testHeadResults'][i].statusCode\r
+            };\r
+            //result['testHeadResults'][i].testHeadResponse = beautify(result['testHeadResults'][i].testHeadResponse, null, 2, 50);\r
+\r
+            this.dataSource[result['testHeadResults'][i].testHeadId + result['testHeadResults'][i].bpmnVthTaskId].push(result['testHeadResults'][i]);\r
+\r
+            if (i == 0) {\r
+              this.selectTestHead(result['testHeadResults'][i].testHeadId + result['testHeadResults'][i].bpmnVthTaskId);\r
+            }\r
+          }\r
+          //keep track of previous results so you don't reload them\r
+          this.lastVTHResultsLength = result['testHeadResults'].length;\r
+\r
+          this.testResult = Object.assign({}, result);\r
+          // this.user.get(result['executor']).subscribe(res => {\r
+          //   this.testResult['executor'] = res;\r
+          // });\r
+          //\r
+\r
+\r
+          this.spin = false;\r
+        }\r
+\r
+\r
+          //only gets called once\r
+        if (!this.refreshData && loopNum == 0 && (result['historicTestDefinition'] && result['historicTestDefinition']['bpmnInstances'][0])) {\r
+          let id = result['historicTestDefinition']['bpmnInstances'][0]['bpmnFileId']\r
+\r
+          if(!this.viewer){\r
+            this.bpmnFactory.setup({\r
+              mode: 'viewer',\r
+              options: {\r
+                container: '#canvas'\r
+              },\r
+              fileId: id\r
+            }).then(res => {\r
+              this.viewer = res;\r
+              this.updateFlowData();\r
+            });\r
+          }else{\r
+            this.bpmnFactory.getXml({\r
+              fileId: id\r
+            }).then(res => {\r
+              this.viewer.setBpmnXml(res);\r
+              this.updateFlowData();\r
+            })\r
+          }\r
+\r
+        }\r
+      }\r
+    );\r
+\r
+  }\r
+\r
+  updateExecutionData(){\r
+      if(this.executionJobLog){\r
+          this.executionJobLogDataSource = {};\r
+          for(var val = 0; val < this.executionJobLog.length; val++){\r
+              var elem = this.executionJobLog[val];\r
+\r
+              let exists = false;\r
+              Object.keys(this.executionJobLogDataSource).forEach((e, val) => {\r
+                  if(e == elem.activityId){\r
+                      exists = true;\r
+                      return;\r
+                  }\r
+              });\r
+\r
+              if(!exists){\r
+                  this.executionJobLogDataSource[elem.activityId] = [elem];\r
+              }\r
+              else{\r
+                  var found = false;\r
+\r
+                  this.executionJobLogDataSource[elem.activityId].forEach( (value, index) => {\r
+                      if(this.executionJobLogDataSource[elem.activityId][index].id == elem.id){\r
+                          this.executionJobLogDataSource[elem.activityId][index] = elem;\r
+                          found = true;\r
+                      }\r
+                  })\r
+                  if(!found){\r
+                      this.executionJobLogDataSource[elem.activityId].push(elem);\r
+                  }\r
+              }\r
+              if(val == 0){\r
+                  this.selectExecutionJobLog(elem.activityId);\r
+              }\r
+          };\r
+      }\r
+\r
+      if(this.executionExternalTaskLog){\r
+          this.executionExternalTaskLogDataSource = {};\r
+          for(var val = 0; val < this.executionExternalTaskLog.length; val++){\r
+              var elem = this.executionExternalTaskLog[val];\r
+\r
+              let exists = false;\r
+              Object.keys(this.executionExternalTaskLogDataSource).forEach((e, val) => {\r
+                  if(e == elem.activityId){\r
+                      exists = true;\r
+                      return;\r
+                  }\r
+              });\r
+\r
+              if(!exists){\r
+                  this.executionExternalTaskLogDataSource[elem.activityId] = [elem];\r
+              }\r
+              else{\r
+                  var found = false;\r
+\r
+                  this.executionExternalTaskLogDataSource[elem.activityId].forEach( (value, index) => {\r
+                      if(this.executionExternalTaskLogDataSource[elem.activityId][index].id == elem.id){\r
+                          this.executionExternalTaskLogDataSource[elem.activityId][index] = elem;\r
+                          found = true;\r
+                      }\r
+                  })\r
+                  if(!found){\r
+                      this.executionExternalTaskLogDataSource[elem.activityId].push(elem);\r
+                  }\r
+              }\r
+              if(val == 0){\r
+                  this.selectExecutionExternalTaskLog(elem.activityId);\r
+              }\r
+          };\r
+      }\r
+\r
+\r
+\r
+      if(this.executionVariables){\r
+          this.executionVariablesDataSource = {};\r
+          for(var val = 0; val < this.executionVariables.length; val++){\r
+              var elem = this.executionVariables[val];\r
+\r
+              let exists = false;\r
+              Object.keys(this.executionVariablesDataSource).forEach((e, val) => {\r
+                  if(e == elem.variableName){\r
+                      exists = true;\r
+                      return;\r
+                  }\r
+              });\r
+\r
+              if(!exists){\r
+                  this.executionVariablesDataSource[elem.variableName] = [elem];\r
+              }\r
+              else{\r
+                  var found = false;\r
+\r
+                  this.executionVariablesDataSource[elem.variableName].forEach( (value, index) => {\r
+                      if(this.executionVariablesDataSource[elem.variableName][index].id == elem.id){\r
+                          this.executionVariablesDataSource[elem.variableName][index] = elem;\r
+                          found = true;\r
+                      }\r
+                  })\r
+                  if(!found){\r
+                      this.executionVariablesDataSource[elem.variableName].push(elem);\r
+                  }\r
+              }\r
+              if(val == 0){\r
+                  this.selectExecutionVariable(elem.variableName);\r
+              }\r
+          };\r
+      }\r
+\r
+  }\r
+\r
+  calcTime(result) {\r
+    let tsDate = new Date(result['startTime']);\r
+    let teDate = new Date(result['endTime']);\r
+    let ttDate = (teDate.getTime() - tsDate.getTime()) / 1000;\r
+\r
+    result['date'] = tsDate.getMonth() + 1 + "/" + tsDate.getDate() + "/" + tsDate.getFullYear();\r
+    result['startTime'] = tsDate.getHours() + ":" + tsDate.getMinutes() + ":" + tsDate.getSeconds();\r
+    result['endTime'] = teDate.getHours() + ":" + teDate.getMinutes() + ":" + teDate.getSeconds();\r
+    result['totalTime'] = ttDate + ' secs';\r
+\r
+\r
+  }\r
+\r
+  updateFlowData() {\r
+    console.log(this.processInstanceId);\r
+    this.testExecution.status(this.processInstanceId).subscribe(\r
+      result => {\r
+        if (result) {\r
+          let data = result['body'];\r
+          //check process state\r
+          if (data.historicProcessInstance.state == 'COMPLETED') {\r
+            this.processState = 'Completed';\r
+            this.pullData = false;\r
+          } else if (data.historicProcessInstance.state == 'ACTIVE') {\r
+            this.processState = 'Running';\r
+          } else {\r
+            this.processState = 'Failed';\r
+            this.pullData = false;\r
+          }\r
+\r
+          if(data.historicJobLog){\r
+            this.executionJobLog =  data.historicJobLog;\r
+          }\r
+          if(data.historicExternalTaskLog){\r
+            this.executionExternalTaskLog =  data.historicExternalTaskLog;\r
+          }\r
+          if(data.historicVariableInstance){\r
+            this.executionVariables =  data.historicVariableInstance;\r
+          }\r
+          //update execution tabs -- job log, external task log, variables\r
+          this.updateExecutionData();\r
+\r
+\r
+            //loop through processes to get their info\r
+          for (let i = 0; i < data.historicActivityInstance.length; i++) {\r
+            let p = data.historicActivityInstance[i];\r
+            let state = null;\r
+            if (p.startTime && p.endTime && !p.canceled) { // process completed successfully\r
+              state = 'completed';\r
+            } else if (p.startTime && !p.endTime) { // process is still running\r
+              state = 'running';\r
+            } else if (p.canceled) {\r
+              state = 'failed';\r
+            }\r
+\r
+            //get task id\r
+\r
+            //highlight task boxes based on their state\r
+            this.viewer.getModel().get('canvas').addMarker(p.activityId, 'highlight-task-' + state);\r
+          }\r
+\r
+\r
+\r
+          for (let i = 0; i < data.historicIncident.length; i++) {\r
+            let p = data.historicIncident[i];\r
+            if (p.incidentMessage) {\r
+              this.taskLog += p.activityId + ': ' + p.incidentMessage + '\n';\r
+            }\r
+            this.viewer.getModel().get('canvas').addMarker(p.activityId, 'highlight-task-failed');\r
+          }\r
+        }\r
+      },\r
+      err => {\r
+\r
+      }\r
+    );\r
+  }\r
+\r
+  cancelExecution() {\r
+    this.executionService.delete(this.testResult._id).subscribe(result => {\r
+      this.updateFlowData();\r
+    });\r
+  }\r
+\r
+  expand(element) {\r
+    if (this.expandedElement == element)\r
+      this.expandedElement = null;\r
+    else\r
+      this.expandedElement = element;\r
+  }\r
+\r
+  beauty(json) {\r
+    return beautify(json, null, 2, 50);\r
+  }\r
+\r
+  @HostListener('window:resize', ['$event'])\r
+  onResize(event){\r
+    // console.log("hi")\r
+    if(this.viewer)\r
+      this.viewer.resize();\r
+  }\r
+\r
+\r
+  json2html(json: any = [{  }], tabs = 0) {\r
+    var html = '';\r
+    var tabHtml = '';\r
+    if (typeof json === 'string') {\r
+      json = JSON.parse(json);\r
+    }\r
+    for (let i = 0; i < tabs; i++) {\r
+      tabHtml += '&nbsp;&nbsp;&nbsp;&nbsp;';\r
+    }\r
+    for (let key in json) {\r
+      if (json.hasOwnProperty(key)) {\r
+        if (typeof json[key] === "object") {\r
+          html += tabHtml + '<b><u>' + key + ':</u></b><br/>';\r
+          if (json.constructor === Array && toInteger(key) > 0) {\r
+            tabs--;\r
+          }\r
+          html += this.json2html(json[key], ++tabs);\r
+        } else {\r
+          html += tabHtml + '<b><u>' + key + ':</u></b>' + '<br/>';\r
+          if (typeof json[key] === 'string') {\r
+            json[key] = json[key].replace(/\\n/g, '<br/>' + tabHtml);\r
+          }\r
+          html += tabHtml + json[key] + '<br/>';\r
+          html += '<br/>';\r
+        }\r
+      }\r
+    }\r
+    return html;\r
+  }\r
+\r
+  selectTestHead(key) {\r
+    this.selectedTestHead = key;\r
+  }\r
+\r
+  selectTestInstance(key){\r
+    this.selectedTestInstance = key;\r
+\r
+  }\r
+\r
+    selectExecutionJobLog(key){\r
+        this.selectedExecutionJobLog = key;\r
+\r
+    }\r
+\r
+    selectExecutionExternalTaskLog(key){\r
+        this.selectedExecutionExternalTaskLog = key;\r
+\r
+    }\r
+\r
+    selectExecutionVariable(key){\r
+        this.selectedExecutionVariable = key;\r
+\r
+    }\r
+\r
+  call() {\r
+    //\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel.module.spec.ts b/otf-frontend/client/src/app/layout/control-panel/control-panel.module.spec.ts
new file mode 100644 (file)
index 0000000..ee11f53
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ControlPanelModule } from './control-panel.module';\r
+\r
+describe('ControlPanelModule', () => {\r
+  let controlPanelModule: ControlPanelModule;\r
+\r
+  beforeEach(() => {\r
+    controlPanelModule = new ControlPanelModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(controlPanelModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/control-panel/control-panel.module.ts b/otf-frontend/client/src/app/layout/control-panel/control-panel.module.ts
new file mode 100644 (file)
index 0000000..05f7b91
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { ControlPanelRoutingModule } from './control-panel-routing.module';\r
+import { ControlPanelComponent } from './control-panel.component';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { MatButtonModule, MatTableModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatBadgeModule, MatCardModule, MatSelectModule, MatOptionModule, MatIconModule, MatTabsModule, MatListModule, MatDividerModule, MatExpansionModule } from '@angular/material';\r
+import { TestHeadModalModule } from 'app/shared/modules/test-head-modal/test-head-modal.module';\r
+import { AlertModalModule } from 'app/shared/modules/alert-modal/alert-modal.module';\r
+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { CodemirrorModule } from 'ng2-codemirror';\r
+import { RobotReportComponent } from '../robot-report/robot-report.component';\r
+import { DataTablesModule } from 'angular-datatables';\r
+import { ResizableModule } from 'angular-resizable-element';\r
+import { NgxJsonViewerModule } from 'ngx-json-viewer';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    ControlPanelRoutingModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatTableModule,\r
+    MatFormFieldModule,\r
+    MatInputModule,\r
+    MatPaginatorModule,\r
+    TestHeadModalModule,\r
+    AlertModalModule,\r
+    MatBadgeModule,\r
+    PerfectScrollbarModule,\r
+    MatCardModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatIconModule,\r
+    NgbModule,\r
+    CodemirrorModule,\r
+    MatTabsModule,\r
+    MatListModule,\r
+    MatDividerModule,\r
+    MatExpansionModule,\r
+    DataTablesModule,\r
+    ResizableModule,\r
+    NgxJsonViewerModule\r
+  ],\r
+  declarations: [ControlPanelComponent, RobotReportComponent],\r
+  entryComponents: [RobotReportComponent]\r
+})\r
+export class ControlPanelModule { }\r
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard-routing.module.ts b/otf-frontend/client/src/app/layout/dashboard/dashboard-routing.module.ts
new file mode 100644 (file)
index 0000000..187bd80
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { DashboardComponent } from './dashboard.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+      path: '', component: DashboardComponent\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class DashboardRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard.component.pug b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.pug
new file mode 100644 (file)
index 0000000..a823e68
--- /dev/null
@@ -0,0 +1,115 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(style="position: relative")\r
+  .row\r
+    .col-12\r
+      .pull-left\r
+        mat-form-field(style="width:110px")\r
+          input(matInput, [matDatepicker]="fromPicker", placeholder="From Date", [(ngModel)]="stats.filters.startDate")\r
+          mat-datepicker-toggle(matSuffix, [for]="fromPicker")\r
+          mat-datepicker(#fromPicker)\r
+        mat-form-field.ml-2(style="width:110px")\r
+          input(matInput, [matDatepicker]="toPicker", placeholder="To Date", [(ngModel)]="stats.filters.endDate")\r
+          mat-datepicker-toggle(matSuffix, [for]="toPicker")\r
+          mat-datepicker(#toPicker)\r
+        button.ml-2(mat-icon-button, (click)="stats.getDefaultData(_groups.getGroup())") \r
+          mat-icon arrow_forward\r
+          \r
+      .pull-right\r
+        mat-form-field\r
+          input(matInput, [ngModel]="stats.executionList?.length", placeholder="Total Executions", disabled)\r
+  //- div\r
+  //-   button.pull-right(mat-button, (click)="openFilterModal()")\r
+  //-     mat-icon() filter_list\r
+  //-     span(style="font-size: 13px") Filter\r
+\r
+  //-   button.pull-right(mat-button, (click)="resetData()")\r
+  //-     mat-icon() refresh\r
+  //-     span(style="font-size: 13px") Reset\r
+\r
+  .row\r
+    .col-12\r
+      mat-card\r
+        mat-card-content\r
+          app-line-chart(height="201px")\r
+\r
+  .row.mt-2\r
+    .col-lg-5\r
+      mat-card\r
+        mat-card-header\r
+          mat-card-title \r
+            h5 Test Results\r
+        mat-card-content\r
+          app-pie-chart(height="230px")\r
+    \r
+    .col-lg-7\r
+      mat-card\r
+        mat-card-header\r
+          mat-card-title \r
+            h5 Test Definition Usage\r
+        mat-card-content\r
+          app-test-definition-executions-bar-chart(height="230px")\r
+  .row.mt-2\r
+    \r
+    .col-lg-7\r
+      mat-card\r
+        mat-card-header\r
+          mat-card-title \r
+            h5 Virtual Test Head Executions\r
+        mat-card-content\r
+          app-test-head-executions-line-chart(height="230px")\r
+    \r
+    .col-lg-5\r
+      mat-card\r
+        mat-card-header\r
+          mat-card-title \r
+            h5 Virtual Test Head Usage & Status Codes\r
+        mat-card-content\r
+          app-test-head-execution-bar-chart(height="230px")\r
+  //- mat-card.w-100\r
+  //-   mat-card-header\r
+  //-     mat-card-title(style="font-weight: bold") Selected Definitions: \r
+  //-       span(style="color: #4F8CA9") {{TD_selectedTDs}}\r
+\r
+  //-   .row.mb-4  \r
+  //-     .col-md-7\r
+  //-       app-line-chart(height="380px")\r
+\r
+  //-     .col-md-5\r
+  //-       app-pie-chart(height="380px")\r
+\r
+  //- mat-card.w-100\r
+  //-   mat-card-header\r
+  //-     mat-card-title(style="font-weight: bold") Selected Instances: \r
+  //-       span(style="color: #4F8CA9") {{TI_selectedTIs}}\r
+  //-     mat-card-title(style="font-weight: bold") Selected Definitions: \r
+  //-       span(style="color: #4F8CA9") {{TI_selectedTDs}}\r
+  //-   .row.mb-4\r
+  //-     .col-md-7\r
+  //-       app-multi-line-chart(height="380px")\r
+  //-     .col-md-5\r
+  //-       app-horiz-bar-chart(height="380px")\r
+  \r
+  //- mat-card.w-100\r
+  //-   mat-card-header\r
+  //-     mat-card-title(style="font-weight: bold") Scheduled Tests \r
+  //-     mat-card-title(style="font-weight: bold") Selected Instances: \r
+  //-       span(style="color: #4F8CA9") {{sched_selectedTIs}}\r
+  //-   .row\r
+  //-     .col-md-4\r
+  //-       app-schedule\r
+\r
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard.component.scss b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.scss
new file mode 100644 (file)
index 0000000..8099e27
--- /dev/null
@@ -0,0 +1,82 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+mat-card img{\r
+    object-fit: cover; /*this makes de image in src fit to the size of specified below*/\r
+    width: 100%; /* Here you can use wherever you want to specify the width and also the height of the <img>*/\r
+    margin: 0;\r
+}\r
+\r
+.dropdown-toggle::after {\r
+    display:none;\r
+}\r
+\r
+mat-card-content {\r
+    padding: 0px !important;\r
+    padding-top: 0px !important;\r
+}\r
+\r
+.mat-icon-button {\r
+    height: 20px !important;\r
+    padding: 0px !important;\r
+}\r
+\r
+.col-md-4, .col-md-5{\r
+    padding: 0px;\r
+    margin: 0px;\r
+}\r
+.col-md-3{\r
+\r
+    padding:0px;\r
+    margin: 0px;\r
+}\r
+\r
+//\r
+.shadow{\r
+    -moz-box-shadow:    inset 0 0 0 4px #2b2b2b;\r
+    -webkit-box-shadow: inset 0 0 0 4px #2b2b2b;\r
+    box-shadow:         inset 0 0 0 4px #2b2b2b;\r
+}\r
+\r
+.COMPLETED-dash {\r
+    background-color: #0d47a1;\r
+    color: white;\r
+}\r
+\r
+.SUCCESS-dash {\r
+    background-color: #199700;\r
+    color: white;\r
+}\r
+\r
+.FAILURE-dash {\r
+    background-color: #dd2c00 !important;\r
+    color: white;\r
+}\r
+\r
+.STOPPED-dash {\r
+    background-color: #ff9100;\r
+    color: white;\r
+}\r
+\r
+.UNAUTHORIZED-dash {\r
+    background-color: #000000;\r
+    color: white;\r
+}\r
+\r
+.UNKNOWN-dash {\r
+    background-color: White;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard.component.spec.ts b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.spec.ts
new file mode 100644 (file)
index 0000000..ec9b2eb
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { DashboardComponent } from './dashboard.component';\r
+\r
+describe('DashboardComponent', () => {\r
+  let component: DashboardComponent;\r
+  let fixture: ComponentFixture<DashboardComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ DashboardComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(DashboardComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard.component.ts b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.ts
new file mode 100644 (file)
index 0000000..a01d427
--- /dev/null
@@ -0,0 +1,173 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, HostListener, EventEmitter, OnDestroy } from '@angular/core';\r
+import { routerTransition } from '../../router.animations';\r
+import { MatPaginator, MatDialog } from '@angular/material';\r
+import { ListService } from 'app/shared/services/list.service';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { SchedulingService } from 'app/shared/services/scheduling.service';\r
+import { Subject, Observable, Subscription } from 'rxjs';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { FeathersService } from 'app/shared/services/feathers.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { FilterModalComponent } from '../components/stats/filter-modal/filter-modal.component';\r
+import { StatsService } from '../components/stats/stats.service';\r
+\r
+@Component({\r
+  selector: 'app-dashboard',\r
+  templateUrl: './dashboard.component.pug',\r
+  styleUrls: ['./dashboard.component.scss'],\r
+  animations: [routerTransition()]\r
+})\r
+\r
+export class DashboardComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  // Top of the page stats\r
+  public topStats = {\r
+    COMPLETED: 0,\r
+    UNKNOWN: 0,\r
+    FAILURE: 0,\r
+    STOPPED: 0,\r
+    UNAUTHORIZED: 0,\r
+    FAILED: 0\r
+  };\r
+\r
+  public testDefinitionList = null;\r
+  public testExecutions;\r
+  public displayedColumns = ['name', 'result', 'startTime'];\r
+  public displayedScheduleColumns = ['name', 'nextRun'];\r
+  public weekExecutions = 0;\r
+  public weekSchedules = 0;\r
+  public filter = { testResult: '' }; // for dropdown in html\r
+  public group;\r
+\r
+  public eventsSubject: Subject<void>;\r
+\r
+  public TD_selectedTDs = "All";\r
+  public TI_selectedTDs = "All";\r
+  public TI_selectedTIs = "Top 5";\r
+  public sched_selectedTIs = "All";\r
+\r
+  public viewers = [];\r
+\r
+  @ViewChild(MatPaginator) executionsPaginator: MatPaginator;\r
+  @ViewChild(MatPaginator) scheduledPaginator: MatPaginator;\r
+\r
+  constructor(\r
+    private _groups: GroupService,\r
+    private filterModal: MatDialog, \r
+    private stats: StatsService\r
+  ) { }\r
+\r
+  async ngOnInit() {\r
+\r
+    this.stats.getDefaultData(this._groups.getGroup());\r
+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {\r
+      this.stats.getDefaultData(group);\r
+    }));\r
+\r
+    //this.resetData();\r
+\r
+  //   this.stats.onTDExecutionChangeFinished().subscribe(res => {\r
+  //     this.TD_selectedTDs = "";\r
+  //     this.stats.getTDFilters().selected.forEach(item => {\r
+  //       this.TD_selectedTDs += (item.viewValue + ", ");\r
+  //     })\r
+  //     let charLimit = 200;\r
+  //     if (this.TD_selectedTDs.length > charLimit) {\r
+  //       this.TD_selectedTDs = this.TD_selectedTDs.slice(0, charLimit) + "...";\r
+  //     } else this.TD_selectedTDs = this.TD_selectedTDs.slice(0, this.TD_selectedTDs.length - 2);\r
+  //   })\r
+\r
+  //   this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+  //     let selectedTIs = this.stats.getTIFilters().selectedTIs;\r
+  //     let selectedTDs = this.stats.getTIFilters().selectedTDs;\r
+\r
+  //     if (selectedTIs.length == 0) this.TI_selectedTIs = "All";\r
+  //     else {\r
+  //       this.TI_selectedTIs = "";\r
+  //       this.stats.getTIFilters().selectedTIs.forEach(item => {\r
+  //         this.TI_selectedTIs += (item + ", ");\r
+  //       })\r
+  //       let charLimit = 200;\r
+  //       if (this.TI_selectedTIs.length > charLimit) {\r
+  //         this.TI_selectedTIs = this.TI_selectedTIs.slice(0, charLimit) + "...";\r
+  //       } else this.TI_selectedTIs = this.TI_selectedTIs.slice(0, this.TI_selectedTIs.length - 2);\r
+  //     }\r
+\r
+  //     if (selectedTDs.length == 0) this.TI_selectedTDs = "All";\r
+  //     else {\r
+  //       this.TI_selectedTDs = "";\r
+  //       this.stats.getTIFilters().selectedTDs.forEach(item => {\r
+  //         this.TI_selectedTDs += (item + ", ");\r
+  //       })\r
+  //       let charLimit = 200;\r
+  //       if (this.TI_selectedTDs.length > charLimit) {\r
+  //         this.TI_selectedTDs = this.TI_selectedTDs.slice(0, charLimit) + "...";\r
+  //       } else this.TI_selectedTDs = this.TI_selectedTDs.slice(0, this.TI_selectedTDs.length - 2);\r
+  //     }\r
+  //   })\r
+\r
+  //   this.stats.onScheduleChangeFinished().subscribe(res => {\r
+  //     let selectedTIs = this.stats.scheduledTests.map(el => el.name);\r
+  //     //console.log(selectedTIs);\r
+  //     if (selectedTIs.length == 0) this.sched_selectedTIs = "All";\r
+  //     else {\r
+  //       this.sched_selectedTIs = "";\r
+  //       this.stats.scheduledTests.map(el => el.name).forEach(item => {\r
+  //         this.sched_selectedTIs += (item + ", ");\r
+  //       })\r
+  //       let charLimit = 200;\r
+  //       if (this.sched_selectedTIs.length > charLimit) {\r
+  //         this.sched_selectedTIs = this.sched_selectedTIs.slice(0, charLimit) + "...";\r
+  //       } else this.sched_selectedTIs = this.sched_selectedTIs.slice(0, this.sched_selectedTIs.length - 2);\r
+  //     }\r
+  //   })\r
+  }\r
+\r
+  ngOnDestroy(){\r
+    this.toDestroy.forEach(elem => {\r
+      elem.unsubscribe();\r
+    });\r
+  }\r
+\r
+  openFilterModal() {\r
+    let open = this.filterModal.open(FilterModalComponent, {\r
+      width: '50%',\r
+      height: '60%',\r
+      disableClose: true\r
+    })\r
+\r
+    open.afterClosed().subscribe(res => {\r
+      this.ngOnInit();\r
+    })\r
+  }\r
+\r
+  resetData() {\r
+    //console.log("resetting");\r
+    this.TD_selectedTDs = "All";\r
+    this.TI_selectedTDs = "All";\r
+    this.TI_selectedTIs = "Top 5";\r
+    this.sched_selectedTIs = "All";\r
+    this.stats.getDefaultData(this._groups.getGroup());\r
+  }\r
+\r
+}\r
+\r
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard.module.spec.ts b/otf-frontend/client/src/app/layout/dashboard/dashboard.module.spec.ts
new file mode 100644 (file)
index 0000000..9db6f12
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { DashboardModule } from './dashboard.module';\r
+\r
+describe('DashboardModule', () => {\r
+  let dashboardModule: DashboardModule;\r
+\r
+  beforeEach(() => {\r
+    dashboardModule = new DashboardModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(dashboardModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/dashboard/dashboard.module.ts b/otf-frontend/client/src/app/layout/dashboard/dashboard.module.ts
new file mode 100644 (file)
index 0000000..d6545e9
--- /dev/null
@@ -0,0 +1,110 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { DashboardRoutingModule } from './dashboard-routing.module';\r
+import { DashboardComponent } from './dashboard.component';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+\r
+import {\r
+    MatBadgeModule,\r
+    MatButtonModule,\r
+    MatCardModule,\r
+    MatFormFieldModule,\r
+    MatIconModule,\r
+    MatInputModule,\r
+    MatOptionModule,\r
+    MatPaginatorModule,\r
+    MatSelectModule,\r
+    MatTableModule,\r
+    MatTabsModule,\r
+    MatCheckboxModule,\r
+    MatDialogModule,\r
+    MAT_DIALOG_DEFAULT_OPTIONS,\r
+    MatExpansionModule,\r
+    MatDatepickerModule,\r
+    MatNativeDateModule,\r
+    MatProgressSpinnerModule,\r
+} from '@angular/material';\r
+import { TestHeadModalModule } from 'app/shared/modules/test-head-modal/test-head-modal.module';\r
+import { AlertModalModule } from 'app/shared/modules/alert-modal/alert-modal.module';\r
+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { TestDefinitionExpandedDetailsComponent } from '../test-definition-expanded-details/test-definition-expanded-details.component';\r
+import { ViewWorkflowModalModule } from 'app/shared/modules/view-workflow-modal/view-workflow-modal.module';\r
+import { PieChartComponent } from '../components/stats/pie-chart/pie-chart.component';\r
+import { LineChartComponent } from '../components/stats/line-chart/line-chart.component'\r
+import { ScheduleComponent } from '../components/stats/schedule/schedule.component';;\r
+import { HorizBarChartComponent } from '../components/stats/horiz-bar-chart/horiz-bar-chart.component';\r
+import { FilterModalComponent } from '../components/stats/filter-modal/filter-modal.component';\r
+import { MultiLineChartComponent } from '../components/stats/multi-line-chart/multi-line-chart.component';\r
+import { TestDefinitionExecutionsBarChartComponent } from '../components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component';\r
+import { TestHeadExecutionsLineChartComponent } from '../components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component';\r
+import { TestHeadExecutionBarChartComponent } from '../components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        DashboardRoutingModule,\r
+        FormsModule,\r
+        FilterPipeModule,\r
+        MatButtonModule,\r
+        MatTableModule,\r
+        MatFormFieldModule,\r
+        MatInputModule,\r
+        MatPaginatorModule,\r
+        TestHeadModalModule,\r
+        AlertModalModule,\r
+        MatBadgeModule,\r
+        PerfectScrollbarModule,\r
+        MatCardModule,\r
+        MatSelectModule,\r
+        MatOptionModule,\r
+        MatIconModule,\r
+        NgbModule,\r
+        MatCheckboxModule,\r
+        MatTabsModule,\r
+        ViewWorkflowModalModule,\r
+        MatDialogModule,\r
+        MatExpansionModule,\r
+        MatDatepickerModule,\r
+        MatNativeDateModule,\r
+        MatProgressSpinnerModule\r
+    ],\r
+    declarations: [\r
+        DashboardComponent,\r
+        TestDefinitionExpandedDetailsComponent,\r
+        LineChartComponent,\r
+        MultiLineChartComponent,\r
+        ScheduleComponent,\r
+        PieChartComponent,\r
+        HorizBarChartComponent,\r
+        FilterModalComponent,\r
+        TestDefinitionExecutionsBarChartComponent,\r
+        TestHeadExecutionsLineChartComponent,\r
+        TestHeadExecutionBarChartComponent\r
+    ],\r
+    entryComponents: [TestDefinitionExpandedDetailsComponent, FilterModalComponent],\r
+    schemas: [CUSTOM_ELEMENTS_SCHEMA],\r
+    exports: [FilterModalComponent, LineChartComponent],\r
+    providers: [{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true } }, MatDatepickerModule]\r
+\r
+\r
+})\r
+export class DashboardModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback-routing.module.ts b/otf-frontend/client/src/app/layout/feedback/feedback-routing.module.ts
new file mode 100644 (file)
index 0000000..92fee59
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {RouterModule, Routes} from '@angular/router';\r
+import {FeedbackComponent} from './feedback.component';\r
+\r
+const routes: Routes = [\r
+    {path: '', component: FeedbackComponent}\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class FeedbackRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback.component.pug b/otf-frontend/client/src/app/layout/feedback/feedback.component.pug
new file mode 100644 (file)
index 0000000..b38e410
--- /dev/null
@@ -0,0 +1,45 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+    h2 Submit Feedback\r
+    hr\r
+\r
+// name label & form input\r
+form([formGroup]='FeedbackFormGroup')\r
+    .row\r
+        .col-sm-2\r
+            mat-form-field(style='width:100%')\r
+                input(matInput, placeholder='First Name', formControlName='firstName', required)\r
+                mat-error Required\r
+        .col-sm-2\r
+            mat-form-field(style='width:100%')\r
+                input(matInput, placeholder='Last Name', formControlName='lastName', required)\r
+                mat-error Required\r
+    .row\r
+        .col-sm-4\r
+            // email label & form input\r
+            mat-form-field(style='width:100%')\r
+                input(matInput, placeholder='example@.com', type='email', formControlName='email', required)\r
+    .row\r
+        .col-sm-4\r
+            // message label & form input\r
+            mat-form-field(style='width:100%')\r
+                textarea(matInput, placeholder='Message', formControlName='message', required, rows=15)\r
+    .row\r
+        .col-sm-4\r
+            // button for submitting the information in the form\r
+            button(mat-raised-button='', color='primary', class='pull-right', (click)='onSubmitFeedback()') Send Feedback\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback.component.scss b/otf-frontend/client/src/app/layout/feedback/feedback.component.scss
new file mode 100644 (file)
index 0000000..70fad06
--- /dev/null
@@ -0,0 +1,17 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback.component.spec.ts b/otf-frontend/client/src/app/layout/feedback/feedback.component.spec.ts
new file mode 100644 (file)
index 0000000..c79e8c3
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';\r
+\r
+import {FeedbackComponent} from './feedback.component';\r
+\r
+describe('FeedbackComponent', () => {\r
+    let component: FeedbackComponent;\r
+    let fixture: ComponentFixture<FeedbackComponent>;\r
+\r
+    beforeEach(async(() => {\r
+        TestBed.configureTestingModule({\r
+            declarations: [FeedbackComponent]\r
+        })\r
+            .compileComponents();\r
+    }));\r
+\r
+    beforeEach(() => {\r
+        fixture = TestBed.createComponent(FeedbackComponent);\r
+        component = fixture.componentInstance;\r
+        fixture.detectChanges();\r
+    });\r
+\r
+    it('should create', () => {\r
+        expect(component).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback.component.ts b/otf-frontend/client/src/app/layout/feedback/feedback.component.ts
new file mode 100644 (file)
index 0000000..ed1be7f
--- /dev/null
@@ -0,0 +1,98 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {Component, OnInit} from '@angular/core';\r
+import {routerTransition} from 'app/router.animations';\r
+import {FormControl, FormGroup, Validators} from '@angular/forms';\r
+import {MatDialog} from '@angular/material';\r
+import {AlertModalComponent} from '../../shared/modules/alert-modal/alert-modal.component';\r
+import {FeedbackService} from "../../shared/services/feedback.service";\r
+\r
+@Component({\r
+    selector: 'app-feedback',\r
+    templateUrl: './feedback.component.pug',\r
+    styleUrls: ['./feedback.component.scss'],\r
+    animations: [routerTransition()]\r
+})\r
+export class FeedbackComponent implements OnInit {\r
+    private firstName: string;\r
+    private lastName: string;\r
+    private email: string;\r
+    private message: string;\r
+\r
+    public FeedbackFormGroup: FormGroup;\r
+    private FirstNameFormControl: FormControl;\r
+    private LastNameFormControl: FormControl;\r
+    private EmailFormControl: FormControl;\r
+    private MessageFormControl: FormControl;\r
+\r
+    constructor(\r
+        private ResponseMatDialog: MatDialog,\r
+        private feedback: FeedbackService\r
+    ) {\r
+    }\r
+\r
+\r
+    // @ViewChild('feedbackForm') private FeedBackForm;\r
+\r
+    ngOnInit(): void {\r
+        this.createFormControls();\r
+        this.createFormGroup();\r
+    }\r
+\r
+    private createFormControls() {\r
+        this.FirstNameFormControl = new FormControl('', [Validators.required]);\r
+        this.LastNameFormControl = new FormControl('', [Validators.required]);\r
+        this.EmailFormControl = new FormControl('', [Validators.required, Validators.email]);\r
+        this.MessageFormControl = new FormControl('', [Validators.required]);\r
+    }\r
+\r
+    private createFormGroup() {\r
+        this.FeedbackFormGroup = new FormGroup({\r
+            firstName: this.FirstNameFormControl,\r
+            lastName: this.LastNameFormControl,\r
+            email: this.EmailFormControl,\r
+            message: this.MessageFormControl\r
+        });\r
+    }\r
+\r
+    // submit button action\r
+    public onSubmitFeedback() {\r
+        if (!this.FeedbackFormGroup.invalid) {\r
+            // console.log(this.FeedbackFormGroup.getRawValue())\r
+            this.feedback.sendFeedback(this.FeedbackFormGroup.getRawValue()).subscribe(\r
+                (result) => {\r
+                    this.sendFeedbackAlert('ok', 'Feedback sent!');\r
+                },\r
+                (error) => {\r
+                    this.sendFeedbackAlert('warning', 'Please verify form fields are correct.');\r
+                }\r
+            )        }\r
+        else{\r
+            this.sendFeedbackAlert('warning', 'Please verify form fields are correct.');\r
+        }\r
+    }\r
+\r
+    private sendFeedbackAlert(type: string, message: string) {\r
+        this.ResponseMatDialog.open(AlertModalComponent, {\r
+            width: '250px',\r
+            data: {\r
+                type: type,\r
+                message: message\r
+            }\r
+        });\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback.module.spec.ts b/otf-frontend/client/src/app/layout/feedback/feedback.module.spec.ts
new file mode 100644 (file)
index 0000000..b27d504
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {FeedbackModule} from './feedback.module';\r
+\r
+describe('FeedbackModule', () => {\r
+    let feedbackModule: FeedbackModule;\r
+\r
+    beforeEach(() => {\r
+        feedbackModule = new FeedbackModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(feedbackModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/feedback/feedback.module.ts b/otf-frontend/client/src/app/layout/feedback/feedback.module.ts
new file mode 100644 (file)
index 0000000..d614de2
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {CommonModule} from '@angular/common';\r
+import {FeedbackComponent} from './feedback.component';\r
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';\r
+import {FilterPipeModule} from 'ngx-filter-pipe';\r
+import {MatBadgeModule, MatButtonModule, MatCardModule, MatIconModule, MatInputModule} from '@angular/material';\r
+import {AlertModalModule} from 'app/shared/modules/alert-modal/alert-modal.module';\r
+import {PerfectScrollbarModule} from 'ngx-perfect-scrollbar';\r
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';\r
+import {FeedbackRoutingModule} from './feedback-routing.module';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        FeedbackRoutingModule,\r
+        FormsModule,\r
+        ReactiveFormsModule,\r
+        FilterPipeModule,\r
+        MatButtonModule,\r
+        MatInputModule,\r
+        AlertModalModule,\r
+        MatBadgeModule,\r
+        PerfectScrollbarModule,\r
+        MatCardModule,\r
+        MatIconModule,\r
+        NgbModule,\r
+    ],\r
+    declarations: [\r
+        FeedbackComponent\r
+    ]\r
+})\r
+export class FeedbackModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/layout-routing.module.ts b/otf-frontend/client/src/app/layout/layout-routing.module.ts
new file mode 100644 (file)
index 0000000..278d3b7
--- /dev/null
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {RouterModule, Routes} from '@angular/router';\r
+import {LayoutComponent} from './layout.component';\r
+import {AdminGuard} from "../shared/guard/admin.guard";\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '',\r
+        component: LayoutComponent,\r
+        children: [\r
+            {path: '', redirectTo: 'dashboard', pathMatch: 'prefix'},\r
+            {path: 'test-definitions', loadChildren: './tests/tests.module#TestsModule'},\r
+            {path: 'settings', loadChildren: './settings/settings.module#SettingsModule'},\r
+            {path: 'manage-group', loadChildren: './manage-group/manage-group.module#ManageGroupModule'},\r
+            {path: 'feedback', loadChildren: './feedback/feedback.module#FeedbackModule'},\r
+            {path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardModule'},\r
+            {path: 'scheduling', loadChildren: './scheduling/scheduling.module#SchedulingModule'},\r
+            {path: 'onboarding', loadChildren: './onboarding/onboarding.module#OnboardingModule'},\r
+            {path: 'control-panel', loadChildren: './control-panel/control-panel.module#ControlPanelModule'},\r
+            {path: 'test-heads', loadChildren: './virtual-test-heads/virtual-test-heads.module#VirtualTestHeadsModule'},\r
+            {path: 'test-instances', loadChildren: './test-instances-catalog/test-instances-catalog.module#TestInstancesCatalogModule'},\r
+            {path: 'test-executions', loadChildren: './test-executions-catalog/test-executions-catalog.module#TestExecutionsCatalogModule'},\r
+            {path: 'user-management', loadChildren: './user-management/user-management.module#UserManagementModule', canActivate: [AdminGuard]},\r
+            {path: 'modeler', loadChildren: './modeler/modeler.module#ModelerModule'}\r
+            \r
+        ]\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class LayoutRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/layout.component.html b/otf-frontend/client/src/app/layout/layout.component.html
new file mode 100644 (file)
index 0000000..a809598
--- /dev/null
@@ -0,0 +1,21 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<app-header></app-header>\r
+<app-sidebar></app-sidebar>\r
+<section class="main-container">\r
+    <router-outlet></router-outlet>\r
+</section>\r
diff --git a/otf-frontend/client/src/app/layout/layout.component.scss b/otf-frontend/client/src/app/layout/layout.component.scss
new file mode 100644 (file)
index 0000000..2ac9020
--- /dev/null
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.main-container {\r
+    margin-top: 56px;\r
+    margin-left: 235px;\r
+    padding: 15px;\r
+    -ms-overflow-x: hidden;\r
+    overflow-x: hidden;\r
+    overflow-y: scroll;\r
+    position: relative;\r
+    overflow: hidden;\r
+}\r
+@media screen and (max-width: 992px) {\r
+    .main-container {\r
+        margin-left: 0px !important;\r
+    }\r
+}\r
+@media print {\r
+    .main-container {\r
+        margin-top: 0px !important;\r
+        margin-left: 0px !important;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/layout.component.spec.ts b/otf-frontend/client/src/app/layout/layout.component.spec.ts
new file mode 100644 (file)
index 0000000..5fd88f2
--- /dev/null
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';\r
+import {RouterTestingModule} from '@angular/router/testing';\r
+import {TranslateModule} from '@ngx-translate/core';\r
+\r
+import {LayoutComponent} from './layout.component';\r
+import {LayoutModule} from './layout.module';\r
+\r
+describe('LayoutComponent', () => {\r
+    let component: LayoutComponent;\r
+    let fixture: ComponentFixture<LayoutComponent>;\r
+\r
+    beforeEach(\r
+        async(() => {\r
+            TestBed.configureTestingModule({\r
+                imports: [\r
+                    LayoutModule,\r
+                    RouterTestingModule,\r
+                    TranslateModule.forRoot(),\r
+                ]\r
+            }).compileComponents()\r
+                .then((arg) => {\r
+                    // handle\r
+                })\r
+                .catch((err) => {\r
+                    // handle\r
+                });\r
+        })\r
+    );\r
+\r
+    beforeEach(() => {\r
+        fixture = TestBed.createComponent(LayoutComponent);\r
+        component = fixture.componentInstance;\r
+        fixture.detectChanges();\r
+    });\r
+\r
+    it('should create', () => {\r
+        expect(component).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/layout.component.ts b/otf-frontend/client/src/app/layout/layout.component.ts
new file mode 100644 (file)
index 0000000..07e06c1
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+\r
+@Component({\r
+    selector: 'app-layout',\r
+    templateUrl: './layout.component.html',\r
+    styleUrls: ['./layout.component.scss']\r
+})\r
+export class LayoutComponent implements OnInit {\r
+    constructor() {}\r
+\r
+    ngOnInit() {}\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/layout.module.spec.ts b/otf-frontend/client/src/app/layout/layout.module.spec.ts
new file mode 100644 (file)
index 0000000..ff64010
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { LayoutModule } from './layout.module';\r
+\r
+describe('LayoutModule', () => {\r
+    let layoutModule: LayoutModule;\r
+\r
+    beforeEach(() => {\r
+        layoutModule = new LayoutModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(layoutModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/layout.module.ts b/otf-frontend/client/src/app/layout/layout.module.ts
new file mode 100644 (file)
index 0000000..1de7fa6
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { TranslateModule } from '@ngx-translate/core';\r
+import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';\r
+\r
+import { LayoutRoutingModule } from './layout-routing.module';\r
+import { LayoutComponent } from './layout.component';\r
+import { SidebarComponent } from './components/sidebar/sidebar.component';\r
+import { HeaderComponent } from './components/header/header.component';\r
+import { RightSidebarComponent } from './components/right-sidebar/right-sidebar.component';\r
+import { CreateGroupModalModule } from 'app/shared/modules/create-group-modal/create-group-modal.module';\r
+import { MatMenuModule, MatIconModule, MatButtonModule } from '@angular/material';\r
+import { MenuItemComponent } from 'app/shared/components/menu-item/menu-item.component';\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        LayoutRoutingModule,\r
+        TranslateModule,\r
+        NgbDropdownModule.forRoot(),\r
+        CreateGroupModalModule,\r
+        MatMenuModule,\r
+        MatIconModule,\r
+        MatButtonModule\r
+    ],\r
+    declarations: [\r
+        LayoutComponent, \r
+        SidebarComponent,\r
+        HeaderComponent, \r
+        RightSidebarComponent,\r
+        MenuItemComponent\r
+    ]\r
+})\r
+export class LayoutModule {}\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.pug b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.pug
new file mode 100644 (file)
index 0000000..03ba7da
--- /dev/null
@@ -0,0 +1,26 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+\r
+h4.mb-2.ml-1(style="font-weight: bold;") Change Roles - {{user?.firstName}} {{user?.lastName}}\r
+  //input.ml-1(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.roleName')\r
+div(style="max-height: 300px; overflow-y: scroll")\r
+  .px-4.py-3\r
+    .mr-2.ml-2(*ngFor="let role of roles")\r
+      mat-checkbox([(ngModel)]="role.isSelected") {{role.roleName}} \r
+div(style="text-align: center")            \r
+  button.primary.mr-1(mat-raised-button, [disabled]= "", aria-label='Edit', color="primary", (click)='saveRoles()') Save\r
+      \r
diff --git a/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.scss b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.spec.ts b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.spec.ts
new file mode 100644 (file)
index 0000000..1eca9b5
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { DropdownMultiselectComponent } from './dropdown-multiselect.component';\r
+\r
+describe('DropdownMultiselectComponent', () => {\r
+  let component: DropdownMultiselectComponent;\r
+  let fixture: ComponentFixture<DropdownMultiselectComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ DropdownMultiselectComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(DropdownMultiselectComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.ts b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.ts
new file mode 100644 (file)
index 0000000..df065cb
--- /dev/null
@@ -0,0 +1,96 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';\r
+import { UserService } from 'app/shared/services/user.service';\r
+\r
+\r
+@Component({\r
+  selector: 'app-dropdown-multiselect',\r
+  templateUrl: './dropdown-multiselect.component.pug',\r
+  styleUrls: ['./dropdown-multiselect.component.scss']\r
+})\r
+export class DropdownMultiselectComponent implements OnInit {\r
+\r
+  public group;\r
+  public memberRoles;\r
+  private params;\r
+  public roles;\r
+  public user;\r
+  public userId;\r
+  public search;\r
+  constructor(private groupService: GroupService, public dialogRef: MatDialogRef<DropdownMultiselectComponent>,\r
+    private userService: UserService,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) {\r
+   \r
+   }\r
+\r
+  ngOnInit() {\r
+    this.search = {};\r
+    this.userId = this.input_data["user"][0]["_id"];\r
+    this.group = this.input_data["group"];\r
+    this.memberRoles = this.group["members"].filter(member => member.userId == this.userId)["roles"];\r
+    this.userService.get(this.userId).subscribe((result) => {\r
+      this.user = result;\r
+    });\r
+    this.roles = this.group.roles;\r
+    \r
+    this.memberRoles = this.group.members.filter(member => member.userId.toString() == this.userId.toString())[0].roles;\r
+    if(this.memberRoles){\r
+      for(let i = 0; i < this.roles.length; i++){\r
+        this.roles[i].isSelected = false;\r
+        for(let j = 0; j < this.memberRoles.length; j++){\r
+          if(this.roles[i].roleName == this.memberRoles[j]){\r
+            this.roles[i].isSelected = true;\r
+          }\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  saveRoles(){\r
+    let member = {\r
+      userId : this.userId,\r
+      roles : []\r
+    }\r
+    \r
+    member.roles = this.roles.filter(role => role.isSelected).map(item => {return item.roleName});\r
+    \r
+    // the logic to remove the one member from the array of members and then push the new member roles\r
+    this.groupService.get(this.group._id).subscribe((res) => {\r
+      let group = res;\r
+      \r
+      let newMembers = [];\r
+      if(group["members"]){\r
+        newMembers = group["members"].filter(member => member.userId.toString() != this.userId.toString());\r
+      }\r
+      newMembers.push(member)\r
+      let groupPatch = {\r
+        _id : this.group._id,\r
+        members : newMembers\r
+      }\r
+      this.groupService.patch(groupPatch).subscribe((response) => {\r
+        this.dialogRef.close();\r
+      });\r
+    });\r
+    \r
+    \r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.pug b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.pug
new file mode 100644 (file)
index 0000000..d4de15a
--- /dev/null
@@ -0,0 +1,96 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  .card-md-12\r
+    .pull-left\r
+      app-page-header([heading]="'Custom Role Management'") \r
+      h5 {{ groupName }}\r
+      \r
+      \r
+\r
+    .pull-right\r
+      button(mat-raised-button color="primary", (click)="update()") Update All Rows\r
+      \r
+  .card-md-12.mt-3\r
+\r
+    div(style="width: 100%", [hidden]="!loading")\r
+      mat-spinner(style="margin: auto", color="primary")\r
+    table(mat-table, [dataSource]="dataSource", style="width: 100%", [hidden]="loading")\r
+\r
+      ng-container(matColumnDef="roleName")\r
+        th(mat-header-cell, *matHeaderCellDef) Role Name\r
+        td(mat-cell, *matCellDef="let element") {{ element.roleName }}\r
+\r
+      ng-container(matColumnDef="read")\r
+        th(mat-header-cell, *matHeaderCellDef) Read\r
+        td(mat-cell, *matCellDef="let element")\r
+          mat-checkbox([(ngModel)]="element.readPermission", [disabled]="true") \r
+          \r
+\r
+\r
+\r
+      ng-container(matColumnDef="write")\r
+        th(mat-header-cell, *matHeaderCellDef) Write\r
+        td(mat-cell, *matCellDef="let element") \r
+          mat-checkbox([(ngModel)]="element.writePermission", [disabled]="(element.roleName == 'admin')") \r
+          \r
+      ng-container(matColumnDef="execute")\r
+        th(mat-header-cell, *matHeaderCellDef) Execute\r
+        td(mat-cell, *matCellDef="let element")\r
+          mat-checkbox([(ngModel)]="element.executePermission", [disabled]="(element.roleName == 'admin')")\r
+         \r
+      ng-container(matColumnDef="delete")\r
+        th(mat-header-cell, *matHeaderCellDef) Delete\r
+        td(mat-cell, *matCellDef="let element") \r
+          mat-checkbox([(ngModel)]="element.deletePermission", [disabled]="(element.roleName == 'admin')") \r
+\r
+      ng-container(matColumnDef="management")\r
+        th(mat-header-cell, *matHeaderCellDef) Management\r
+        td(mat-cell, *matCellDef="let element")\r
+          mat-checkbox([(ngModel)]="element.managementPermission", [disabled]="true") \r
+\r
+      ng-container(matColumnDef="actions")\r
+        th(mat-header-cell, *matHeaderCellDef) Actions\r
+        td(mat-cell, *matCellDef="let element")\r
+          button(color="warn", matTooltip="Delete Role Permissions", mat-icon-button, (click)="deleteRole(element)", [disabled]="((element.roleName == 'admin') || (element.roleName == 'user') || (element.roleName == 'developer'))")\r
+            mat-icon delete_forever\r
+          button(color="primary", matTooltip="Save Role Permissions", mat-icon-button, (click)="update()", [disabled]="(element.roleName == 'admin')")\r
+            mat-icon save\r
+          \r
+\r
+\r
+\r
+      tr(mat-header-row *matHeaderRowDef="displayedColumns")\r
+      tr(mat-row *matRowDef="let row; columns: displayedColumns;")\r
+\r
+     \r
+\r
+\r
+\r
+  div(style="width: 100%;height:50px")\r
+\r
+  .card-md-12\r
+    .row\r
+      .col-sm-4\r
+        h3 Add New Role\r
+    .row\r
+      .col-sm-4\r
+        mat-form-field\r
+          input(matInput [(ngModel)]="roleName", id="roleName", name="roleName", placeholder="Role Name", required)\r
+        button(mat-raised-button color="primary", (click)="create()", style="margin-left:20px;", [disabled]="!roleName") Add Role\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.scss b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.spec.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.spec.ts
new file mode 100644 (file)
index 0000000..f6bab0d
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ManageGroupRolesComponent } from './manage-group-roles.component';\r
+\r
+describe('ManageGroupRolesComponent', () => {\r
+  let component: ManageGroupRolesComponent;\r
+  let fixture: ComponentFixture<ManageGroupRolesComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ManageGroupRolesComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ManageGroupRolesComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.ts
new file mode 100644 (file)
index 0000000..c637e2b
--- /dev/null
@@ -0,0 +1,243 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { routerLeftTransition } from 'app/router.animations';\r
+import { ActivatedRoute } from '@angular/router';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { MatTableDataSource } from '@angular/material/table';\r
+import { MatDialog } from '@angular/material';\r
+import {AlertModalComponent} from '../../../shared/modules/alert-modal/alert-modal.component';\r
+import { NEXT } from '@angular/core/src/render3/interfaces/view';\r
+import { convertPropertyBindingBuiltins } from '@angular/compiler/src/compiler_util/expression_converter';\r
+import { extractDirectiveDef } from '@angular/core/src/render3/definition';\r
+import { take } from 'rxjs/operators';\r
+\r
+\r
+\r
+\r
+\r
+@Component({\r
+  selector: 'app-manage-group-roles',\r
+  templateUrl: './manage-group-roles.component.pug',\r
+  styleUrls: ['./manage-group-roles.component.scss'],\r
+  animations: [routerLeftTransition()]\r
+})\r
+\r
+export class ManageGroupRolesComponent implements OnInit {\r
+\r
+\r
+  public selectedGroup;\r
+  public groupRoles;\r
+  public roleName = null;\r
+  public loading = false;\r
+  public groupPermissions;\r
+  public element = {};\r
+  public groupName;\r
+\r
+  public dataSource;\r
+  public displayedColumns;\r
+  public readPermission = false;\r
+  public writePermission = false;\r
+  public executePermission = false;\r
+  public deletePermission = false;\r
+  public managementPermission = false;\r
+\r
+  \r
+\r
+\r
+\r
+  constructor(\r
+    public _groups: GroupService,\r
+    private cookie: CookieService,\r
+    private modal: MatDialog,\r
+    private ResponseMatDialog: MatDialog\r
+    \r
+  ) { \r
+\r
+    \r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.setComponentData(this._groups.getGroup());\r
+    this._groups.groupChange().subscribe(group => {\r
+      this.setComponentData(group);\r
+\r
+    });\r
+\r
+\r
+  }\r
+\r
+  setComponentData(group) {\r
+    if(!group){\r
+      return;\r
+    }\r
+    this.groupName = group.groupName;\r
+    this.loading = true;\r
+    this.dataSource = new MatTableDataSource();\r
+    this._groups.find({ \r
+      $limit: -1,\r
+      _id: group['_id'], \r
+      $select: ['_id', 'roles']\r
+    }).subscribe((res) => {\r
+\r
+      this.selectedGroup = res[0];\r
+      this.groupRoles = res[0].roles;\r
+\r
+      //If current group does not have any roles\r
+      if ( (this.groupRoles == null) || (this.groupRoles.length < 1))\r
+      {\r
+        this.groupRoles = [\r
+          {roleName: "admin", permissions: "read, write, execute, delete, management"},\r
+          {roleName: "user", permissions: "read"},\r
+          {roleName: "developer", permissions: "read, write, execute, delete"}\r
+        ];\r
+        \r
+      }\r
+\r
+\r
+      for (let i = 0; i < this.groupRoles.length; i++){\r
+        this.groupRoles[i].readPermission = false;\r
+        this.groupRoles[i].writePermission = false;\r
+        this.groupRoles[i].executePermission = false;\r
+        this.groupRoles[i].deletePermission = false;\r
+        this.groupRoles[i].managementPermission = false;\r
+        if (this.groupRoles[i].permissions.includes('read')){\r
+          this.groupRoles[i].readPermission = true;\r
+        }\r
+        if (this.groupRoles[i].permissions.includes('write')){\r
+          this.groupRoles[i].writePermission = true;\r
+        }\r
+        if (this.groupRoles[i].permissions.includes('execute')){\r
+          this.groupRoles[i].executePermission = true;\r
+        }\r
+        if (this.groupRoles[i].permissions.includes('delete')){\r
+          this.groupRoles[i].deletePermission = true;\r
+        }\r
+        if (this.groupRoles[i].permissions.includes('management')){\r
+          this.groupRoles[i].managementPermission = true;\r
+        }\r
+      }\r
+     \r
+      this.dataSource.data = this.groupRoles;\r
+      this.loading = false;\r
+      this.update();\r
+\r
+      \r
+\r
+\r
+    })\r
+\r
+    this.displayedColumns = ['roleName', 'read', 'write', 'execute', 'delete', 'management', 'actions']\r
+\r
+  }\r
+\r
+\r
+  async create(){\r
+    \r
+    for (let i = 0; i < this.groupRoles.length; i++){\r
+      if (this.groupRoles[i].roleName == this.roleName){\r
+        this.sendFeedbackAlert('warning', 'Please do not add a duplicate role name.');\r
+        return;\r
+      }\r
+    }\r
+  \r
+      this.groupRoles.push({roleName: this.roleName, readPermission: true, writePermission: false, executePermission: false, deletePermission: false, managementPermission: false});\r
+      await this.update();\r
+      this.setComponentData(this._groups.getGroup());\r
+  \r
+\r
+  }\r
+\r
+  async update(){\r
+  \r
+    \r
+    for (let i = 0; i < this.groupRoles.length; i++) {\r
+      this.groupRoles[i].permissions = [];\r
+      if(this.groupRoles[i].readPermission){\r
+        this.groupRoles[i].permissions.push('read');\r
+      }\r
+      if(this.groupRoles[i].writePermission){\r
+        this.groupRoles[i].permissions.push('write');\r
+      }\r
+      if(this.groupRoles[i].executePermission){\r
+        this.groupRoles[i].permissions.push('execute');\r
+      }\r
+      if(this.groupRoles[i].deletePermission){\r
+        this.groupRoles[i].permissions.push('delete');\r
+      }\r
+      if(this.groupRoles[i].managementPermission){\r
+        this.groupRoles[i].permissions.push('management');\r
+      }\r
+\r
+    }\r
+      \r
+    this.groupPermissions = this.groupRoles.map(({ roleName, permissions }) => ({roleName, permissions}));\r
+   \r
+    let groupPatch = {\r
+      '_id': this.selectedGroup._id,\r
+      'roles': this.groupPermissions\r
+    };\r
+    //console.log(groupPatch);\r
+    await this._groups.patch(groupPatch).pipe(take(1)).toPromise();\r
+\r
+      \r
+\r
+  }\r
+\r
+  async deleteRole(element){\r
+\r
+    for (let i = 0; i < this.groupRoles.length; i++){\r
+        if (this.groupRoles[i].roleName == element.roleName){\r
+          this.groupRoles.splice(i, 1);\r
+          break;\r
+        }\r
+    }\r
+    await this.update();\r
+    this.setComponentData(this._groups.getGroup());\r
+\r
+\r
+\r
+\r
+  }\r
+\r
+  public sendFeedbackAlert(type: string, message: string) {\r
+    this.ResponseMatDialog.open(AlertModalComponent, {\r
+        width: '250px',\r
+        data: {\r
+            type: type,\r
+            message: message\r
+        }\r
+    });\r
+  }\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
\r
+\r
+\r
+\r
+\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group-routing.module.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group-routing.module.ts
new file mode 100644 (file)
index 0000000..f8f1398
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {RouterModule, Routes} from '@angular/router';\r
+import {ManageGroupComponent} from './manage-group.component';\r
+import { ManageGroupRolesComponent } from './manage-group-roles/manage-group-roles.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '',\r
+        component: ManageGroupComponent\r
+    },\r
+    {\r
+        path: 'manage-group-roles', component: ManageGroupRolesComponent\r
+      },\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class ManageGroupRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group.component.pug b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.pug
new file mode 100644 (file)
index 0000000..e4b7700
--- /dev/null
@@ -0,0 +1,40 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition]).mb-3\r
+  h2 Manage Group\r
+\r
+  .card-mb-12\r
+    div.mb-1\r
+      mat-card-title(*ngIf="hasMembers") {{ group.groupName }}\r
+  \r
+      button.mr-2.pull-right(matTooltip="Remove user", color="warn", mat-raised-button, (click)="removeMembers()", [disabled] = "!hasSelectedRows") Remove\r
+      button.mr-2.pull-right(matTooltip="Edit user roles", color="accent", mat-raised-button, (click)="editRoles()", [disabled]="!hasSelectedRows || multipleRowsSelected") Edit Roles\r
+      button.mr-2.pull-right(mat-raised-button, color="primary", (click)="openUserSelect()") Add Users\r
+      //button.mr-2.pull-right(mat-raised-button, color="primary", (click)="onboardMechid()") Add Mech Id\r
+      button.mr-2.pull-right(mat-raised-button, color="primary", [routerLink]="['manage-group-roles']") Manage Group Roles\r
+     \r
+      mat-card-content\r
+        .clearfix\r
+        ag-grid-angular.ag-theme-material(\r
+          style="width:100%; height: 600px", \r
+          [rowData]="rowData", \r
+          [columnDefs]="columnDefs",\r
+          rowSelection="multiple",\r
+          [rowMultiSelectWithClick]="true",\r
+          (cellClicked)="onCellClicked($event)",\r
+          (rowSelected)="onRowSelected($event)",\r
+          (gridReady)="onGridReady($event)")\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group.component.scss b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group.component.spec.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.spec.ts
new file mode 100644 (file)
index 0000000..dd3ba91
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ManageGroupComponent } from './manage-group.component';\r
+\r
+describe('ManageGroupComponent', () => {\r
+  let component: ManageGroupComponent;\r
+  let fixture: ComponentFixture<ManageGroupComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ManageGroupComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ManageGroupComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group.component.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.ts
new file mode 100644 (file)
index 0000000..e6ea356
--- /dev/null
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { routerTransition } from '../../router.animations';\r
+import { MatDialog } from '@angular/material';\r
+import { UserSelectComponent } from 'app/shared/modules/user-select/user-select.component';\r
+import { MulticastOperator } from 'rxjs/internal/operators/multicast';\r
+import { forEach } from '@angular/router/src/utils/collection';\r
+import { OnboardMechidComponent } from 'app/shared/modules/onboard-mechid/onboard-mechid.component';\r
+import { DropdownMultiselectComponent } from './dropdown-multiselect.component';\r
+import { TabbedLayout } from 'ag-grid-community';\r
+import value from '*.json';\r
+import { take } from 'rxjs/operators';\r
+import { object } from '@amcharts/amcharts4/core';\r
+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';\r
+\r
+\r
+@Component({\r
+  selector: 'app-manage-group',\r
+  templateUrl: './manage-group.component.pug',\r
+  styleUrls: ['./manage-group.component.scss'],\r
+  animations: [routerTransition()]\r
+})\r
+export class ManageGroupComponent implements OnInit {\r
+\r
+  public group;\r
+  public loading = false;\r
+  public users;\r
+  public tableData; \r
+  public hasMembers = false;\r
+  public hasSelectedRows = false;\r
+  private gridApi;\r
+  private gridColumnApi;\r
+  public memberTable;\r
+  public rowData;\r
+  public multipleRowsSelected = false;\r
\r
+\r
+  public columnDefs = [\r
+    { headerName: 'First Name', field: 'firstName', sortable: true, sort: "asc", filter: true, checkboxSelection: true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true},\r
+    { headerName: 'Last Name', field: 'lastName', sortable: true, filter: true },\r
+    { headerName: 'Email', field: 'email', sortable: true, filter: true },\r
+    { headerName: 'Roles', field:'roles', sortable: true, filter: true, editable: true, cellEditor: "DropdownMultiselectComponent", cellEditorParams: function (params) {\r
+        return {\r
+            roles: params.roles,\r
+            userId: params._id\r
+        };\r
+    }\r
+    },\r
+  ];\r
+\r
+  constructor(private groupService: GroupService, private userService: UserService, private modal: MatDialog,) { }\r
+\r
+  ngOnInit() {\r
+    this.group = {};\r
+    //this.tableData = [];\r
+    this.group = this.groupService.getGroup();\r
+   \r
+    \r
+    this.groupService.groupChange().subscribe(group => {\r
+      this.tableData = undefined;\r
+      this.rowData = undefined;\r
+      this.setComponentData(group);\r
+    });\r
+\r
+    this.groupService.get(this.group._id).subscribe((res) => {\r
+      this.group = res;\r
+      this.setComponentData(this.group);\r
+    });\r
+\r
+    \r
+  }\r
+\r
+  openUserSelect(){\r
+    this.modal.open(UserSelectComponent, {\r
+      width: "500px",\r
+      data: {\r
+          groupId: this.group._id\r
+      }\r
+    }).afterClosed().subscribe((response) => {\r
+      this.groupService.get(this.group._id).subscribe((res) => {\r
+        this.group = res;\r
+        this.setComponentData(this.group);\r
+      });\r
+\r
+    });\r
+    \r
+      \r
+  }\r
+\r
+  onboardMechid(){\r
+    this.modal.open(OnboardMechidComponent, {\r
+      width: "500px",\r
+      data: {\r
+          groupId: this.group._id\r
+      }\r
+    }).afterClosed().subscribe((response) => {\r
+\r
+    });\r
+  }\r
+\r
+  removeMembers(){\r
+    let membersToRemove = this.gridApi.getSelectedRows().map(({_id}) => ({_id}));\r
+    this.group.members = this.group.members.filter(member => membersToRemove.filter(user => member.userId.toString() == user._id.toString()).length <= 0); \r
+    let groupPatch = {\r
+      _id : this.group._id,\r
+      members: this.group.members\r
+    }\r
+    //removes the members from the group\r
+    this.groupService.patch(groupPatch).subscribe(\r
+      (res) => {\r
+        this.gridApi.deselectAll();\r
+        this.tableData = this.tableData.filter(member => membersToRemove.filter(user => member._id.toString() == user._id.toString()).length <= 0);\r
+        this.rowData = Object.assign([], this.tableData);\r
+      }, \r
+      (err) => {\r
+        this.modal.open(AlertModalComponent, {\r
+          data: {\r
+            type: "alert",\r
+            message: "The was an error removing the user. " + err\r
+          }\r
+        });\r
+    });\r
+    \r
+  }\r
+\r
+  setComponentData(group){\r
+    this.gridApi.deselectAll();\r
+    if(!group){\r
+      return;\r
+    }\r
+    this.loading = true;\r
+    this.group = group;\r
+    this.users = [];\r
+    //this.tableData = [];\r
+    //console.log("Running Data")\r
+    this.hasMembers = true;\r
+    this.columnDefs[this.columnDefs.length-1]["cellEditorParams"]["values"] = this.group.roles;\r
+    if(this.group.members){\r
+      \r
+      //console.log(this.group)\r
+      for(let i = 0; i < this.group.members.length; i++){\r
+        let temp = this.group.members[i]["userId"];\r
+        this.userService.get(temp).subscribe(\r
+        (res) => {\r
+          let member = res;\r
+          member["roles"] = this.group.members[i].roles.join();\r
+          if(!this.tableData){\r
+            this.tableData = [];\r
+          }\r
+          if(this.tableData.filter(user => user['_id'].toString() == member["_id"].toString()).length <= 0){\r
+            this.tableData.push(member);\r
+          }else{\r
+            this.tableData = this.tableData.filter(user => user['_id'].toString() != member["_id"].toString())\r
+            this.tableData.push(member);\r
+          }\r
+         // console.log(this.tableData);\r
+          this.rowData = Object.assign([], this.tableData);\r
+        });\r
+        \r
+        \r
+      }\r
+    }else{\r
+      this.hasMembers = false;\r
+    }\r
+    \r
+    \r
+    //need to either populate user or pull each user's info\r
+    //this.rowData = this.tableData;\r
+    //console.log(this.rowData);\r
+  }\r
+\r
+  editRoles(){\r
+    //console.log(this.tableData);\r
+    this.gridApi.refreshCells();\r
+    let memberToEdit = this.gridApi.getSelectedRows().map(({_id}) => ({_id}));\r
+    this.modal.open(DropdownMultiselectComponent, {\r
+      width: "500px",\r
+      data : { \r
+        user : memberToEdit,\r
+        group: this.group\r
+      }\r
+    }).afterClosed().subscribe((res) => {\r
+      this.groupService.get(this.group._id).subscribe((res) => {\r
+        this.group = res;\r
+        this.setComponentData(this.group);\r
+      });\r
+    })\r
+  }\r
+\r
+  onCellClicked(event) {\r
+    //console.log(event.colDef.field)\r
+  }\r
+\r
+  onRowSelected(event){\r
+    if(event.api.getSelectedNodes().length > 0){\r
+      this.hasSelectedRows = true;\r
+      if(event.api.getSelectedNodes().length > 1){\r
+        this.multipleRowsSelected = true;\r
+      }else{\r
+        this.multipleRowsSelected = false;\r
+      }\r
+    }else{\r
+      this.hasSelectedRows = false;\r
+      this.multipleRowsSelected = false;\r
+    }\r
+  }\r
+\r
+  onGridReady(params){\r
+    this.gridApi = params.api;\r
+    //console.log(params.columnApi.autoSizeColumns)\r
+    this.gridColumnApi = params.columnApi;\r
+\r
+    //auto size the column widths\r
+    this.gridColumnApi.autoSizeColumns(['name']);\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group.module.spec.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group.module.spec.ts
new file mode 100644 (file)
index 0000000..1c86cf2
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ManageGroupModule } from './manage-group.module';\r
+\r
+describe('ManageGroupModule', () => {\r
+  let manageGroupModule: ManageGroupModule;\r
+\r
+  beforeEach(() => {\r
+    manageGroupModule = new ManageGroupModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(manageGroupModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/manage-group/manage-group.module.ts b/otf-frontend/client/src/app/layout/manage-group/manage-group.module.ts
new file mode 100644 (file)
index 0000000..b0c4580
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';\r
+import { ManageGroupComponent } from './manage-group.component';\r
+import { ManageGroupRoutingModule } from './manage-group-routing.module';\r
+import { AgGridModule } from 'ag-grid-angular';\r
+import { UserSelectModule } from 'app/shared/modules/user-select/user-select.module';\r
+import { DropdownMultiselectComponent } from './dropdown-multiselect.component';\r
+import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { MatProgressSpinnerModule, MatIconModule, MatCardModule, MatCheckboxModule, MatTableModule, MatButtonModule, MatInputModule, MatFormFieldModule, MatTooltipModule} from '@angular/material';\r
+import { ManageGroupRolesComponent } from './manage-group-roles/manage-group-roles.component';\r
+import {PageHeaderModule} from '../../shared';\r
+import {AlertModalModule} from 'app/shared/modules/alert-modal/alert-modal.module';\r
+import { OnboardMechidModule } from 'app/shared/modules/onboard-mechid/onboard-mechid.module';\r
+\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    MatCardModule,\r
+    ManageGroupRoutingModule,\r
+    AgGridModule.withComponents([DropdownMultiselectComponent]),\r
+    NgbDropdownModule,\r
+    UserSelectModule,\r
+    OnboardMechidModule,\r
+    MatCheckboxModule, \r
+    MatTableModule,\r
+    MatInputModule,\r
+    MatFormFieldModule,\r
+    MatTooltipModule,\r
+    PageHeaderModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatIconModule,\r
+    MatProgressSpinnerModule,\r
+    ReactiveFormsModule,\r
+    AlertModalModule\r
+  \r
+  ],\r
+  declarations: [ManageGroupComponent, DropdownMultiselectComponent, ManageGroupRolesComponent]\r
+    \r
+  \r
+})\r
+export class ManageGroupModule { }\r
diff --git a/otf-frontend/client/src/app/layout/modeler/color-picker/ColorPicker.js b/otf-frontend/client/src/app/layout/modeler/color-picker/ColorPicker.js
new file mode 100644 (file)
index 0000000..3495cdd
--- /dev/null
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {\r
+    is\r
+  } from 'bpmn-js/lib/util/ModelUtil';\r
+  \r
+  \r
+  /**\r
+   * A basic color picker implementation.\r
+   *\r
+   * @param {EventBus} eventBus\r
+   * @param {ContextPad} contextPad\r
+   * @param {CommandStack} commandStack\r
+   */\r
+  export default function ColorPicker(eventBus, contextPad, commandStack) {\r
+  \r
+    contextPad.registerProvider(this);\r
+  \r
+    commandStack.registerHandler('shape.updateColor', UpdateColorHandler);\r
+  \r
+    function changeColor(event, element) {\r
+  \r
+      var color = window.prompt('type a color code');\r
+  \r
+      commandStack.execute('shape.updateColor', { element: element, color: color });\r
+    }\r
+  \r
+  \r
+    this.getContextPadEntries = function(element) {\r
+  \r
+      if (is(element, 'bpmn:Event')) {\r
+        return {\r
+          'changeColor': {\r
+            group: 'edit',\r
+            className: 'icon-red',\r
+            title: 'Change element color',\r
+            action: {\r
+              click: changeColor\r
+            }\r
+          }\r
+        };\r
+      }\r
+    };\r
+  }\r
+  \r
+  \r
+  \r
+  /**\r
+   * A handler updating an elements color.\r
+   */\r
+  function UpdateColorHandler() {\r
+  \r
+    this.execute = function(context) {\r
+      context.oldColor = context.element.color;\r
+      context.element.color = context.color;\r
+  \r
+      return context.element;\r
+    };\r
+  \r
+    this.revert = function(context) {\r
+      context.element.color = context.oldColor;\r
+  \r
+      return context.element;\r
+    };\r
+  \r
+  }
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/color-picker/ColoredRenderer.js b/otf-frontend/client/src/app/layout/modeler/color-picker/ColoredRenderer.js
new file mode 100644 (file)
index 0000000..957e8b9
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import inherits from 'inherits';\r
+\r
+import {\r
+  attr as svgAttr\r
+} from 'tiny-svg';\r
+\r
+import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer';\r
+\r
+import {\r
+  is\r
+} from 'bpmn-js/lib/util/ModelUtil';\r
+\r
+\r
+export default function ColoredRenderer(\r
+    config, eventBus, styles,\r
+    pathMap, canvas, textRenderer) {\r
+\r
+  BpmnRenderer.call(\r
+    this,\r
+    config, eventBus, styles,\r
+    pathMap, canvas, textRenderer,\r
+    1400\r
+  );\r
+\r
+  this.canRender = function(element) {\r
+    return is(element, 'bpmn:BaseElement') && element.color;\r
+  };\r
+\r
+  this.drawShape = function(parent, shape) {\r
+\r
+    var bpmnShape = this.drawBpmnShape(parent, shape);\r
+\r
+    svgAttr(bpmnShape, { fill: shape.color });\r
+\r
+    return bpmnShape;\r
+  };\r
+}\r
+\r
+inherits(ColoredRenderer, BpmnRenderer);\r
+\r
+ColoredRenderer.prototype.drawBpmnShape = BpmnRenderer.prototype.drawShape;\r
+\r
+\r
+ColoredRenderer.$inject = [\r
+  'config.bpmnRenderer',\r
+  'eventBus',\r
+  'styles',\r
+  'pathMap',\r
+  'canvas',\r
+  'textRenderer'\r
+];
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/color-picker/index.js b/otf-frontend/client/src/app/layout/modeler/color-picker/index.js
new file mode 100644 (file)
index 0000000..049e921
--- /dev/null
@@ -0,0 +1,24 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import ColorPicker from './ColorPicker';\r
+import ColoredRenderer from './ColoredRenderer';\r
+\r
+export default {\r
+  __init__: [ 'colorPicker', 'coloredRenderer' ],\r
+  colorPicker: [ 'type', ColorPicker ],\r
+  coloredRenderer: [ 'type', ColoredRenderer ]\r
+};
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/LogTestResultRenderer.js b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/LogTestResultRenderer.js
new file mode 100644 (file)
index 0000000..607cd08
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import inherits from 'inherits';\r
+\r
+import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';\r
+\r
+import {\r
+  is\r
+} from 'bpmn-js/lib/util/ModelUtil';\r
+\r
+import Cat from '../image';\r
+\r
+import {\r
+  append as svgAppend,\r
+  create as svgCreate\r
+} from 'tiny-svg';\r
+\r
+\r
+export default function NyanRender(eventBus) {\r
+  BaseRenderer.call(this, eventBus, 1500);\r
+\r
+  this.canRender = function(element) {\r
+    return is(element, 'custom:Log');\r
+  };\r
+\r
+\r
+  this.drawShape = function(parent, shape) {\r
+    var url = Cat.dataURL;\r
+\r
+    var catGfx = svgCreate('image', {\r
+      x: 0,\r
+      y: 0,\r
+      width: shape.width,\r
+      height: shape.height,\r
+      href: url\r
+    });\r
+\r
+    svgAppend(parent, catGfx);\r
+\r
+    return catGfx;\r
+  };\r
+}\r
+\r
+inherits(NyanRender, BaseRenderer);\r
+\r
+NyanRender.$inject = [ 'eventBus' ];
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/index.js b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/index.js
new file mode 100644 (file)
index 0000000..a73db29
--- /dev/null
@@ -0,0 +1,22 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import LogTestResultRenderer from './LogTestResultRenderer';\r
+\r
+export default {\r
+  __init__: [ 'logTestResultRenderer' ],\r
+  logTestResultRenderer: [ 'type', LogTestResultRenderer ]\r
+};
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/cat.gif b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/cat.gif
new file mode 100644 (file)
index 0000000..bf0314f
Binary files /dev/null and b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/cat.gif differ
diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/index.js b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/index.js
new file mode 100644 (file)
index 0000000..29fce79
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// inlined and base64 encoded ./cat.gif\r
+\r
+module.exports.dataURL = 'data:image/gif;base64,R0lGODlh9AFeAaIHAAAAAP+Z/5mZmf/Mmf8zmf+Zmf///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJBwAHACwAAAAA9AFeAUAD/3i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3fQK7vfO//wKBwSCwaj8ikcsks3p7QqFTSrFqv2Kx2yxVOv+AwiTgom8/otHrNbrvf8Lh8Tq/b7/j4UMzv04x5eAGDhIWGh4iJiouMjY6PkJGSk4+BdkZ+mZocgJZ1lKChoqOkpaaGnnSYm6ytVEWpZaeztAEEt7i5urW6vQS1wI2xq67Fxp2pwcqgvs28zbjL0oXDTsbXrchtz9C509/g4eKLb8TY533abNzdt+Pv8PG15dbo9mHqa/L7/IhZs+280aJH5IKWewhfkXnTr6EyIwIiSpxIsWJFIw4TEcxisf+jx4nmErLKpyajyVMQP6qkiPEkNTdcVsqUGHJKlywjSKZxqSzgL1opZwpVSUTeRiFDk36sKeUmlpywGPIE5rNWUKVYIxaNdzRI1q9a64EhArYjTgs60UxdK+lq2bcgnVaBS9ci0yhk64a9YjCqG7aAHbnVC1buXMJ670LRgtiwDzjyAgYWXASx5cuYMy8V+4UxYcc9IMeTPJnRYM2oU6tWqlhTTLpcRJee5vPW6dW4c+u23DrTa7hpz0Cq/XM2qNuZDShfzrx53t11m0tn/rxubz+/3wY3M7y28VDIMU8fXx362/HTywPnLJIB6CWyv/MLf9nz7iFC7Vtn317BeyX/8cknD328cQQdfjPpBxt//UGlRYACEjcLgRIVYOGFGGaooYbmdZjahiCGqOF1DXYQm1QCEiIhSpXNJOKLHHoo42Uw1pghiSWqsB0d3VWVYgAURmRjjfkhmFiL66ln0ZBD4pgjCjvO0SNpKQYpAJMvFonUZ0hq16VHWNbo5JMmRBkLdz9a9WVq/7XJxJEFkTmDmzmcaeedeFbj1Yx83segnHjRmeeghBYK05Z9JqramICWYCRudEYq6aSUVupUo9ghCqmlnHbq6aecYpqOpgs+hdaaXzHaGaqp/umAlSupKqpNpCbJ16lKZiVrU6zq6moDsBL166yjglqmm8Qq1Oax/7cm6+yz0EYr7bTUVmvttdhmq+223Hbr7bfghivuuOSWa+656Kar7rrstuvuu/DGK68roNZr771nzesuvvz2668X+oa7kKEEF2zwwQh31UPA1Q6c8MMQRyxxHnsw/KSZdqapMVcI72oxDn4dvPHI4iTs8cc1YAwhyYasCFA7I+tZMcojhbwOLS6zrPM+Cv9AczY264OzdzsXzXEbJ/8cQ5RGy/fPKTmb0rMPfZmqdAZMN23c06ZEXcrUC+Pa7NVVD7Gy1u8Eu9lWU4G9BJwzX5012vuo7VFLbR+qYFlJ64ivg2ajSLctVLKYa2Zsv+P2Dn7Gic/fIsw9uI8T9opZ4v/jLK5D43GvWquXY08g+eBpW24evoqyNGzKBsL93tnfFP6j3anXrlvfKewN+n+w0wbzxrTbLjybq/ORHd9b9E56y7UFP/zz0O/VeaC6+/qg4MsvQpzz0XdfO+6sV4/V6NoTTTf3WKEn3eHeq+/co6VOD7T4rAVd0iNeG42+Uu5TB7/3E+nfcthnPfn1B1/Ky15bTFcgLJzuc2Zp3X4c960T/UVj+QMPAysUJhABEIAdfBH4GmXBbWDQfKWwUgg39EHvrTBEI7SWyrBXPsoJSIUvxJCW9uQ6Hu4OghTJ4YZi2DD7CQJ/KHTaBoUkRAvtEAiNWaJMKNTEGxVvXDNMhgL/TSPFCf7LMT0EAtn8Y8RCbZGLBGzgFw0TRp/BK1ITi6McB/C/FtoxLhRUGhznyMeH1fGOdyTitf6omTUa8pCIpNMY3QPEVlmNAvuTHsCuEUlJBqFsjUyKIOVFSEeGTlmZHMomX1BJATipkqN8Y72Y9R8ZKtJRj1wk4DzFyve4Elmw/KQsd8nLXvryl8AMpjCHScxiGvOYyEymMpfJzGY685nQjKY0p0nNalqTmonMpjZxec0cbfOb4LxJNy8WznKaswnj9OY518nOPKZzEw7rozznSU+ZufGdtBJCPffJz37KwYD4DF8Q/EnQghYUoAEdQxkJdcaGvuRgqZRmFj3h/9CKmuyKCX3VQgdVUYde1J0ZFZs+E9bRhn4UoSGNwERvRrcMikJ2KbLnJVOKtY3yaHJJLAVMBSRTMdIUAysVWktzSoqdyqen9/xpBYJ6v6HasGu/0xhSqaZUkQ6UhkUlakm3OgjN5aCqVgVCAifhUq521KsAAOtSbToAdkTVrHBFq1ohyVa3dgOueA2AXOcKyqteMK+lu8LLnvo1vaGUkbEcJvkA+w2u6VSrotjrWhMrzMUyVhqOzSphSSFZulI2mJa97CRQWQS2oLWUEfVWaEUbCdJi7iSn7WKsMKrKeAqVtRpM4xODYFrDOtCLkxwL5EKw2p1tdhSlTNAQjOJbH/+uJrUe4Ncs/WpC0h1XFMmd4nKPxobsahek1LPXdMWK1aZdN7edrM924YFWzgXXc+KNXF2Xd97jyNZWQGAu0u6L35k+LpT10yUEiovbRnj3h58aHnRNlN7xffYBBC7wIg6MPHspmLYw0K0mH6xR2zZVwqPlL2pQJ7wFc0KC8XtdeWP31tmJGJAwFhZ4F4Pi/jpmrMEw6ncoHOMWmngD9Cugiv/6Dh1v7cU9TjIeDytQK0SxTTimSourhGQlK/nHJzheYZK34oZur8pWDnOK/ZvPGnsSC1G2bvPALOY2n5nMvAqyKLlM5K1+WcNuzjNw4Rze39q4CmnGqU94rOdC0wT/w2X2M4LJW+caGpllhLaIAJWDZ+FN2gCVVu6MXbOFPTO6uo0oK6TZnJRLZ7p2pm5wgJlsvE6P+dMsDTVkdRbpiqQawM+7tXP/TFVKuprXoeky8+pLslpTRNdQjDGyf/BkVqMDgcK+rLGX7OQD4dqUZq6ws88B7UZLeNqHVnRuVG3JJjT7vSXqNqhBDCRSz1ncm9r13bK95U1jq4SxTpGoF4jnKhbA0Obx94WwbA9831bfsw5xv6sIcOgI3EIEfzad1x2hhPOb3FdieMN18/ACRBxQTN0JEonNFhw2cbc+OLe8hbzyID7843IKuVpG/mjAmFyIKO+BypMNbB7IpOMw/yeTzIVD8ykfeeEn13TL38zzRTN7JUBHtGrZKocpGX02N89hzn3OpVPPW7dRt7e5hv4JuIK73O10N7bFri+y39SsZ1972pHQxl7LjeoZM7valT531Eq9W3s0qODngPGN3+7v6wr84BffXJ0b/nuIV5fiGU/5NBT+8c+NfLomX/nO0/HamM882891+Vf3/fSolxQvS9/z1Lv+9V1YPeg9Dfva257DKGO902/P+94fQfZLZzk6w/r01uuA22o/pbuDjsXZf1fAEF6+5m3g99EfwLXbXqTu1wb9Dm//ItNvcvA3bH3so1uxzp8t7hH7fdVZ/7/td7+zzc9n9Bsrl60sIv83x7uEbsZXocuifwEIgFbgf6uEf7YkgPlHgFXAVw74gBAYgRI4gRRYgRZ4gRiYgRq4gRzYgR74gSAYgiI4giRYgiZ4giiYgiq4gizYgi74gjAYgzI4gzRYgzZ4g8bkezp4LzgYXTv4g7TUgycGhERIKUI4hEWYhPt3hJikhE5oGEyoAU84hY4RhTVFhVjIBVa4AB7meV74hRRzfi7YhWBYhma4ODVIhme4hmxYBtlHgmrYhnIIhm84gnE4h3hYeXUogneYh34oeHsYgn34h4TYT4EYgW4nJewWMx0TfuOUiFW3iBtzUmL4gJAYB5I4iY34fj91iYGWiS5BifUHgZ7/GG2g2FsQ5YgGOIh5d4o8tYmHGFCl6G2uOBmi6FMVmIhqRnKSUHO2mArMV0y6KGi+2ItX9x1TFTa5iHefWHG8GAnFCBjJyAMXOIxOFY1W5wuM6AnBSEzWiDb7RlbHaBzTuAPVyIymuDHhaIx3pYncqIrX9I1as47QOI6zUY7HZ4HyaF4WJ47tKFXACI/WFGHsiI21eFSNl1QTSJD1+IwHiZD7xYk0xZDZCA0P2TSdtZDzNVgGeZGlkZESSJFF948eyTIgiYgbCTX9WJLkmJB2p5F9aFcWyZIm6ZLKSIEiSZOEkFmjQI+QcJIDli/CmJI6CQk8+VIrGQlAGX3rB3yB/0OLRTlhWMCR9kgJS+l9BXhMOVmURxkKPlkJNkmNxNd/WkmUUWlgU6mSDgmWETl/QumNZnmWUilYatmRwhCW5jiWSoBMW8lu9JdfaxFbXvd1sbh5cVmSf/kDqNhdv2Z8XzVGfQliiekDi7kGWlZvhYkukflt0vdaJiGY8Udto/gxm1lgk9kDlakGl8l0CvkzpYlbp8kDqWl5jbl7N6lHh+mRsbkDs4kGqyl8rUkzr8lau6kDvXkGv+lgEklKwwUCw1kaa0kZg4mZgMleeLk5h7ecLiBd8hWT9GWXaBSa5Mdb3GWZe8d3o/kE3ElcuVk00YmW08maslmeqnmez1eJ6v/ZnB/wnJPxnuGZftpWnYpznTngXukJMjzYnU9JcfMInnMpnu9GntbZlgAaoAdKffrpg965iyRpCnHnmeDQXtmZmVCSoQy2ocQ4k5UTn8A5nxPKmCzaoreZaMVHe3uplzzQjH5pn7ZZKRemnS0Qo+DXfcDSng8Zd/eZYCUmkPzHdXWXBE0IawdHk0iqfvXyoySaZRAablk5WQuab1TKoxbqKViKnwg6fqvWgDi6AzoqmWJKnUpqO92oUluKdjfqpSMFlRdZpTJ2pUsKpCwgpPKnpnhKXWDKknzKfaBSphcqfubWdbyTjlLWoVQmqKGXOnMalPDWo13QppqlommSqJf/ejlMqqDV9qQ34ak9WZWTIaqjqkZmiqGbOqaGoapISak39KavaqC4KFyzCqc3JqkyuQvAo6u7Om6lyp6/Kp9OYateyaqB4arHaqMzGmdXsHO1KqxDg6tKZKnT6iGZioTLOp5opq3X2A7S+q2YmqwaSm9pegXOyjJ3VqfqmmThyn7jGqHlqqdnNK8VWq95dq9cWJvACmjmCo5r5q0A22MCS0ZytnVMEK8k469ourAb17DXR7DMCh8H26CDZqwW2z0Ym5zkCq8dy48fq7AhG0jseqLuWrJWILEjQ7FNt7KjirFRehiOCQBR9pWVSq8rsWwpp2yTprI4u6ZJQK05Kqk+/5urKhu0RQu0uSG0joeqeelrD4ueUvphjNC03Sq1H0G1TgpIYss4kBqrxfKy+mqoU6oIXrtjIHtsUfuvfFK22Gm1+Yi1aguxS8uvhfC2Rwe2HmG3BUq0AmS0LbsCJLu2WytyjgC4WBe3ATS3FTs8hAsA2PqS9KKxMtq3DJoIkFsacXe5PUa6Z9uoBXcvMps9H7q36lW5Wcu4wXlAqnuyHdW6+UqqsOu6squ5DaJuh2qaksulpzqiNdqnxWt6vku79rK6y4O712ptu5u7MDu77QG8bSu8T4u8Omu8Q5ukj4q3jyl0teu3jAW9yRtvNauo3au81botBse1zuig/iBF/v9ms2URdlkKchMXvMYRuoWQdS+Ev2Chv2irgCZrvoABwDtpvxpHwFhhwKhLLfHruD/CwIMgwCsEwVkhwb06ddejwGyBwe2GdDjHwUrhwdabLRU8c2lCwhocQiicwi+XuCLRwkT3wkkpnRh3vzM8FCq8vNqCw2iiw/6ZCDHcQT8MxDUMqPCLjiJMwoHVww9spevLqXeLxYWrEkH8vobJij27wy6RxGHCt1ustGa7sz/XxPuLwI17BxXpDC5mwlqntVWLxllMq9/7EV0slu+yj24rxidBxlhixph7ulfcuWnMx2x8wM0HxkwryCZByExiyJl7x3qMyR3Rx1e7L1D8uYj/IMXjQMlDYsmIfLwFu8dg0sgTLC6ADLqSnBGkbCOmLL6HrMZQx8ofzC6zCMoNhb46aMva98mWgFfA7HvCDJnEHAjGPLx2envJTDa97L+/7Mxyt4PRfHeQrEVwZ81FmM24uc0UpXfby77IjMfjq81fWjDNXM6Eic3onFa1BWWFWE+Cy8FHy79uUs/2TLdLbBc2/MT7zM/zdM8QnM8xJygELU8GTcAITb4DvdB81ND4+9DkFNESLUcUbbMWrU4YndETs9Er29F6i8rEk4UoPXdOadIjltIuzU4rrcqL8tI0XU4xrcknXdM6nU03Pbait9NAbUg9vcg/HdRG3S9Dnccz/33UTG2i4ZzIhdTUUn2AskSvB4G0Z5zJfnwMyXdFxZnOQ1m5hqx8lkrSTLmlZA2hZg0tVv2WfQXV77rL84PWXt2ZbTwvbd2UDqvWAV2i3prW/ky8rTzMYm3HZFmocF29Qsxpf13XZd3XX5zYykmk+FrY5rzCvtHV5WfXjlzVgT2ohOpZfO3EZ/rZz7zVosPZg63M98eAoHFLA9ikUBqPVC3b4uTGVYiAwzeQtW2qr43bUKjbTLCKn1JLv03Br+Taw03brW3bsQfccmHcy83bze3buY3cS6isXbqF3N3d3v3d4B3e4j3e5F3e5n3e6J3e6r3e7N3e7v3e8B3f8j3f9Ctd3/Z93/id3/q93/zd3/793wAe4AI+4ARe4AZ+4Aie4Aq+4Aze4A7u4AkAACH5BAkHAAcALAAAAAD0AV4BQAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd9Aru987//AoHBILBqPyKRyySzentCoVNKsWq/YrHbLFU6/4DCJOCibz+i0es1uu9/wuHxOr9vv+PhQzO/TjHl4AYOEhYaHiImKi4yNjo+QkZKTj4F2Rn6ZmhyAlnWUoKGio6SlpoaedJibrK1URallp7O0AQS3uLm6tbq9BLXAjbGrrsXGnanByqC+zbzNuMvShcNOxtetyG3P0LnT3+Dh4otvxNjnfdps3N234++0XPCQ5dZjWejo6mvz/f6i8hq180arHhETWvKd26fmn8OHAYYImEixosWLGCcagWdw/48FIxlDihRgTmEUhmkgqpwncaTLixvfdfTyscjLmyTtmTwJ681KaQN/sRSCs+hNIrVmXjHKNGPJnTh6uvm5LGi/lk2zVkRa0A0XrWCfSkkYAyUaqminYQXLVmQXLG3jhhTLE4sMs2fS6g22Vq7fnG+r/B0M2CMMkH7pbsBrpt/AvbX6Ep5MubJlpzpdIJarWANjWfMeQ54l+bLp06i1dkaxOW5gK3BGiwvq7lHr1Lhz67Zs+G7p3Rg/D4BEW6jsb7dPG1jOvLlzIsD/Op/eHPrg3mV/R98qdduj4sfBJTdNvbz17W3LUz+fmKaYr5y7rwtPf9D4y1p03w+ehTD2L//wuSYfP/XRt19l+eV2oEUJ/vUfVA689kNsBRoC3jsLClDAhhx26OGHIG6I3oiVhWjiiRw+CCEDEvpAYYWEXDhOhijWCCJO7MUXhH82uWTjjym6t6IKwslBHG0wSkIjkD/iqJ1qRF3X40hM/qjikPeQEcuRViXJ5UBP4tfimHC1dxCWNhQZC2hewhMmb2TG2YSDmaGZHRZr5qnnnnyqkSGJgFK2mp1QaNHnoYgmesmUgTZ62qCEssZoW3JWaumlmGaqaZ2RTvGnk5uGKuqopJZ6RKd8fHoUpxC8ydSZneYoIKwRqPoSpKimY6lvleZaQaYjXOorNsLeKeewEwArQrH/yDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678Ma7k6n01msvPvLCe+++/PYrZL7faqnowAQXbPDBaVwJcLMCI+zwwxBHrMq/Cw+p5pptZiyTwbhWDMbFW2os8jcHd+yxpwPmMfIiMp4iWpvV0HryJsKx087KOP+jFBAzu1IzLS3nLHQ4O//QczYppzT0ygExEnQpRfuAEL5HL/Dz0iI3zTKSXbVhck12VW110mdhfZyrRsU0TtQ9XGCrS1+De7XZkKE9GVezsL3EbnEHTHZedA/SpZtR8j1EUl5tYbjM7M5t9uAYFu6oRvZOzh3j/2NR/YLjgaNl9+L0Wk5R3x+8PRLpD3AezsuBfy7668Ch3oHpbrG62N+NhXZz5xFJDvvvjcrOyaRs3fsi71tDzgjtwDcPvMLDyxqWvccjn8jTijDv/PaWQ0+k67ipfj3X1mdPPHnqPQf+5Omr7/usPP+xPmriI4J9+YRoD2WZ4Z+PGf9mit97FKejIVQPf8vQX1YalBpVMbCARksVAeEnhAMW6H7SWFKVUMQ97m2wRt6DiqUsWB8MJtB/FfkgBzvoPBWeKITYAtlUvkM+rGnQhR8C1Y6ktEM6Se8iOAwRDK8lQ+84woQju2EQOaRDIPCohwF8H0aW+KEhWquIlkDgKP/mR0F/vcaHVgwXFgOhRYBIkX5eHBMYKbYuQ0nsjXBkgwJZODnhpcuNccxjHudIx+DZDl18HF0aB0nIQtpLbGP74QL/2AAuziWMC3Hk/7wXSMphDpGvUGRTOiZJ/rARS5pc5CVZhMJNMhKTjdyVseJ0RUwFS5Wo/BUsNzdLaSkrBMyKpS53ycte+vKXwAymMIdJzGIa85jITKYyl8nMZjrzmdCMpjSnSc1q6tKQ2Mxmr6xpJ21685tv4WY3wUnOcjZBnGgypzrXeUp0hqFheoynPOcZCEi6kwXwpKc+98nPhH3ynjXIZz8HSlA92hOgnsEdn8qIv5K1E6GtUuieGFr/PoeOEqLJkqieKGo9ix4UomNUGUd559F/YjSTBgzZ42poCtYlKWYfRWhIlbZS5ZXCpTCCqUlPGlGBCoJuSKQETiuk0yDwNAMzLVtNhzoKptanqAI8qix9qlSXsXSkHNXbDqTqNonarBtYDWsAtKoDroItpTOcRVDFStLEXdSsiUSrEa1qU7YWQmtUIWsOphY2RNbPrlfZwhGvSgq9AoCvV0DlXwFLOC0Mtq6jMCxiraBYrzJWJXgdH2RFIdkSkMWvlr0scs44PbyJo7NT7eTlYrqtxYr2FKplUBE44tYhVrIwOzWXa19bitiuVgi09dpDD3BbO2ZrtyLbrFpIaxrT/5rCsLdtolH1FVqgOnW5UFTQ4bomxwn27626re5SoRHY7H43CIgT7gPRCN5yIVdjys0gc8UEXO6uIUDnzW1ANacZ8fIWGL69W+VeZ1xJ9ZWWVAXcf5UR4CeGjsDD5VViVxkEEi7YFA3m4YNFV+ATfBbBcp0PPK6bswz38cQwifCySvkqFaO0gj7RHVh5Z2IU2xi30z0Mi9Pm4lr5d3W761yNb3ziDnc1lDxu71lhnNZ3kBhnQyYyHY28ZPOWll4Wxt9a7bNjKXu5gfpFapelS6osl2/LvUPyl9dM36iuWM1kHpWZrYfm6LL5zqvK8SvhnGcsx3ikdR6zdNrHHD6/jv/Qhc4waz1gaO0mOHePfTLy7OwSRC+n0ZaztAEwDbcwezjKxfuxZiVNY0H7RdOcdhSqFe3pyf4Awo9mk0AIq0VKj2TV820erq0caj1T2AewDjGBIh1kjtq6dgBkr93WS6lWSzjZV2byXC9sG1NDUDCOXnZ/NBzBATJblMJuCLUjcexHQru5gv52tKUmQXW3ONbDGTckyj1JK+gn3dteI7tRNcI/JwnNpNkxFT2E5xENvEOLRpqc5jwagMNW4AcXUcG3E/ENJZwV/W4yjByOYYhHfOIUr/jFh5VUBc86vlnz+MHjzAMHv5rbvLZIxQswcl+VHNInJ3WblEhFlu/A5cD/hrkTfSRyZ1Pr5rJ2Gq2hrPKB+1wHQO9B1F9OpaL7Wm4ajcOXir00ni/x6TmY+s+FTnWRzLzmuUL6HOQNamSzMwn6dnPjso6xcbfd3G9HQty77S61G8nuuUZQ3uEexau3MQsFTfya6A3yLsq9Z3hUvOTxwPjG9xrt44r85Dc/h8pbfn+YF5fmOU/62gb+87HrsbeKO/jWu56QlU11vV9P+9qXKvYBtr3udx8q3J++z7wP/r7gCgLWK5m4vwf7YfnNavAaP/TUlb0nbZt84POdUNKf/eNTmX3ZHt/3MTfl8ZtveFCSP7fPN7rYctnfbR7dlW8+ljHZ3wL6Q+uWxa+l/zDtvwL+Owv/paN/weR/KUCAyAKAjCaAxLeADNiADviAEBiBEjiBFFiBFniBGJiBGriBHNiBHviBIBiCIjiCJFiCJniCKJiCKriCLNiCLviCMBiDMjiDNNhMwneDw1eDCYiDPHh7Ojg7PRiEovKDQCiERgh/RJhQR7iE8peEYsaEUNgiTqiEUViFXTCFpCRtpbeFXJgH0DeB8NaFYjiGZ/CFEhiGZJiGXGiGEYiGaviGm8eGEOiGcFiHBSWHD0iHdriH+4SHDqiHfBiI8eSH9+R3WidvL8UxqgdSdKdSiFggJVV+DWiIDPeIaBGJ2zeJjZgMlgiJivh9R0WJ/taJx/+Bidc3h5voCaT4VJ9IiMskihq3ipBhivt2hqkoYljDcTnnDBkDVaeYh7c4bLm4dKGgc3vhi7XYhsEobuM1Y2rFdTmVClRWTWoXOLqodM7oJcjYNhRYjdaFcpNgjHqxjTxQgd7YjOQFNNBIVNK4iDK1jDQ1jOAoCeKYFuS4Vd0Ij1W1NNeYPOkIM+0Iijz1XluXjbIoMqhli/D2Vf94kAhpepLIgARJQ/PokKUIkZkokaImCv1okXmFkb/4hxtZjMTokfSRkMq4kOpYkSZ5jCCZjKioks/IkhSVWSuBkrjEX0czkS3ZCDZ5CB2ZCDiZfwdWNTzZk4vwkxZSkpQwlAH/WJQ7OZJIqSSCtYsGCTUvyY1ZMmGgJZNTuUVViY31KAxZWY6epZOQJ5VfWW2OZZUN+VxliY9bSVmYdJRraQhKWQhBiQhOuYNcuX5quZbnh14bo17U132/FZHnYpcWOZhAEFzdNX7Wdjru2FqB+ZWO+QOQeV/DlX6KGV5eeZeIkJk+sJl+0pmT6Xbqt3qXOZWk2QOm6U+SiZiCJJB+E5qiiZfVdyuzVZiReZi5V5nawpgO+Zo8EJtowEmpiXer2S3EeZB3p32waV+n6W6CZ5usiZv8OJYdF35t9pjUKZv5ll+f6V6tmTE0GXDeCSf1lTdxSXjZ1pzc8pz0kZ4Pt57X/0mY7mmY54Zu2Omc59km9tmdQwc64Lmfv9mf31me5EKf4TGgvbWbhVea4Zmc3gVm/zmfAeolEEoK0Tl9B3oK0LWclyeflqmdQ9OhYImf0XEvsDONe/aX7Rdu8ZibusmiqVcvLyqcBYiW/behJvmh4rdh3cOjBiaj9QekHimk70akdWSknwaV+KSkjSmhGOqkjgKj8UeXOoaiNsqkSYalfpShPyqlZUqj+2ij+WOlymYqO0qm32OmcYqmJqema4qjBloqb+qKT4ikU+ql08CdMAKmqJejfIp8tGlJI+egzLCOXcemhco9WvpieNppcBpXWiiM4yCoFUKokRqfDDqXlf9KmZeqAIwqVI46NJ76qVdqoreTqDiWkX2aqczoZKkqNKvKqo8CpUcWnKWKqHSKc5t6qyUGqbr6O5PqY7DaYacajsTKdKN6rM6TrD3lq34Wi2UUaLAqrWyGeZ5ZZqPIUNqaq9yKrK4aISQKoqVSiZ0zrsZargXnremaYvXCrtbIlHdKrvDKYefKfdZqKvb6jTrnefsqqf2ahf+6ruGarfjKZdtasDcmr8tKPQurRe4arWyxawV6YhpbdtemlTH6rokJrthqsQ2bZmDasUGHYiordWQnl8/msWMarEnnj886afPaFC3bcja2s2O3dzD7ay5bpIC6lCoKrSlraQ+rGz7/C3UvW1byI7KC0pp7yTQ5yxRNG3Y9q7TGuXwzsLQfOyEVaz8n2zpXaxRZCwA3lrZiB7Vfq6/MWWFjC5RlSzcESxFsu7WI9rCHiqkyGyjvVbVJdLZFkbcsy7VSO7Jm+baJ27Y8wK6Cm3Jgq5rYRp4bS6pL8bR71W7jWaJyW7KyeLeKam+germUOyea67XvdKHrBgQBS22iG6tMcG98Zp1DGpJ10bmtK7agu4qxa7uO+7OWmrlA67arC7zW57pzS4q/q7v+qW0KCnq4WyisK72fO21VOrlxi7qW+7fSuTepqyvIy5tF24nNG71TW7vO67kg+zHVC260WqMVErnk1nQ9/wexfnF2Bxul63u714uLG1e3BJprM4e/+Wt1sgohGYe9FyTAEYpkBWzAcaG/oaoQCwzA8+vAHmq/XyfBbUHBCSxClfK6VEG/88bBQeTBH4zA07siF6ypGXy0lOB1KazCYAHCLazAI7y8smHCbPkmEWzDWYHDMDlOC8fDDafBKwrEFad8wRu8Tlt1Ede3+XCObsmLZkPDOOTEqQvFWivFB0fF+qCPdWqzV4mrKLzFyTu0E8qzxevGZsfCRZydNLt2FMmp4aHFLsTFb6y2qRvFcTzF+5t2ZCysZvyWqprGe7zGcBy2wuvIbBwSRNy+WAeIGKwIPuw5iqxCfNzGj+x43v8rc3JMybdZx39HbGdcrBDcxIz8yc2Gsacbyik0yosreoVcs5isxPVJG6tqhH1svO0Ciwz8Wr0shL+8ue8izJdMzI0bpjx4zKobzLdsx7DbzEXhy55MyuqizDDMW8UchNAcL9xcqwv2zT0YztFnynnCdtasfLWHzsk8zad8Yeb8zNlcy9KMJ4I4ebGLv9QKoPq8z4rXzxD7zxoa0AJ9h4QrxNubwwAzegnNTwRdsAZ9olcQ0Yk30fta0cOJeBhNUBoNrxx9XB790f0U0uU60oQ8sVbY0kEIfqa7uy4907YH07LcpDSd0zVdlwut0z69012ZsD891Otk0ysLykSd1N//ZNSRzL5KrdQh+K1PndRR3dOoCcsNjc9GjNXfq80I27gqDZpC7dCmas2/mgl8e0pSPce/tNZeja5cra5s7cJdC8ypY9VnnS9urdXKGtNOzdfmZ9ZqjddibMssDZw37b9zrcOC7XyEPcjystdBS6lN/coVPMaNTUmPfdlBzUpCKyGtZIAYINq7RNpn6X7TgoBF2ITEZNqiOiahrYCvytrD5NohSyaxjdpPSdv7J9s9qtvRotrRw9sD6NtH6tnvZ9sZBdzFzdxnitypjYRESdxYWN3Wfd3Ynd3avd3c3d3e/d3gHd7iPd7kXd7mfd7ond7qvd7s3d7u/d7wHd/yPd/0Il3f9n3f+J3f+r3f/N3f/v3fAB7gAj7gBF7gBn7gCM4BCQAAIfkECQcABwAsAAAAAPQBXgFAA/94utz+MMpJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo9IHGDJbDqf0Kh0Sq1ar9isdsvteq/JsJhmHZjP6LR6zW673/C4fE6v2+/4vLw67vs/ZXqCg4SFhoeIiW58f41/WIpwAZOUlZaXmJmam5ydnp+goaKjn5GLYI6pPJCmbaSvsLGys7S1lq1sWKq7OayFtsDBAQTExcbHwsfKBMLNnYa6vNJkV4fO16/L2snaxdjfldCo0+Qvvm7c3cbg7O3u75tw0eX0K+euwerI8PzAX1H9QMkbF+IflHpD7rEJyLBhLINPPOlbF2ygFRIQnSAUonD/jcOPIANUEUCypMmTKFOStMLQIqMKVlTKnFly3kZz1SSF3NlvJM2fKFkGdEnlQkygSFcSvNmioxqe3yYyC+gzqdWZWIQRhXi1q0qbTFk4TQMVm1SGVb2qFZC14puMTdbKBRtWxVg0ZfOCSyu3r0y4gJf4Hfx3ad0Ud8/oXeyML+HHgQE/nlzT8OETic0wnMhYmGPKoEOLHv2T7mUNcON0hieVGCgspGPLnk1b7cvTEFLrXN2u9VRPsGsLH0589m0fkZNTUf3JN293wYUbmE69OvXoxQlb3z4dO+HjPZSLh8JcYuvn7bzP5r5dfXa57K277wsehOT3NDMPQM+f0nzS//cJ919QcIFWHyAF4odVTm/019+AogVYG4QmSfhdUSVQGNp4UZTnICXO8UNhASSWaOKJKKZIooIsjqbiizCWeKAHGhrI4RMefjjMeSJe8VOMQKbY4pCUBWmkjBjOcOMXFOg3Bygh6viajzQdeSRQNVr1GX1bpmSlkTPiBhODuJAlJVpUFLfkmlxRZpqYIjiJyJloTqEmm3h2YeNFcGKmW5mABipoGlkSaehgb/bpAmCDNupoK4UeKqltlim6KJX55anpppx26umnT1iaEKYLgmrqqaimquoUonJEql+JOtDlWnxudNRksSoQaaa1toqcpjJs2mqnIwjr6w/GwpCsov/ExgnssdBGK+201FZr7bXYZqvtttx26+234IYr7rjklmvuueimq+667Lbr7rvwxrvBqvTWa+8/8n5777789ntQvtAG8ujABBdssB1hAlyOwAc37PDDAyesMBJyWkPnxfA4muvESpA5CMadRGkLZxeL0yvHvzL8McibiFwLyXSaLDHKNeiXDsws59zQVlLQDITN+fCo89D98ByFz8h6vBDROkdm3llunXIygoBxDDTTLDsdstC2GP2vs1VPfDXWvM1KKRUtvVXpA7dCtra+SntE9nNmeyVU0WpPHUHbF+od7thzQ91TmsS1BYzXXtzpN7iAz71Y3fjZO2lli8Ot8tL/jncG+XuST87W29427g7Ojm/u+enZbcyt6L3pk7l/hKMuu6GqH/snOq8/Tfomu87uO+ozH3Y7Prlz4jLvr/6u/PKU9xyEv1Pk2DLXxWfSe1/xVXe9odlflzysSRab4KSsY3J89ZZsT+v4E37/Fft9O48R/LTH/RT60LkPIP2yZWkhouEDm+lwlSfpOeh8e9FfSb4UJOb5joFACh4G1Lc+PBmwPwj8xoggCCMHzo6DMJKgCe7VJPvhAUrUY9oGQSik0ijwbFLYk52QwkIViTBD9irh5fSAQsGRbYU1NBGWXmi32LnNiFUK4oluaKmKGQJ/sxjgEaE3HhkGcFxO/AUU/x+CRNpQ8UZWZNW5GAWxMj6Mgh6sX+XapaEvuvGNcDQV0ibQxjja8Y54VM4cJYDGCoGOAVIc4hUXFkgXrvEAfWzeIPeoKyJq6Y8LKCSvxGgrSZZKYolUyiF9tqxL5WlYnBLfJxnJtmcpy5TMCqUA2UTKVrrylbCMpSxnScta2vKWuMylLnfJy1768pfADKYwh0nMYhrzmMhMpjKXycxm2ieP0Iwmk5zpB2la85qQpObzsMnNbjZBm33wpji5Cc6kLceM6EynOvXAxHI+85zrjKc850moRboTMVWgpz73mc523lNWJmzFFqGosWxqM4uEGCj+CrpJcCJ0ZQotHkP9qf/MhwoiotWbqD3/aVG5BS6Fs9jdh2S20Xt29H4f9eHLXFeyQtTuoAG1Q+YyCAuROoiklPwnQHeYh5mCVBY27Q9O5afTBpSvpj/FqEIRp5Gi7jSfuxlZUpW6RaZ+06lGjalmgqZSqkbUqkzAalZ5aiav5kVrxpvqK8C6hPmFTWFHNWtD0Dq9rs6CrQBwK1ysptX9yLUsdNUETdeaN4qW8q0Ai+tfm2HJwqBtKIUtKSAb+76Gakuxiw0GZVNyN37g1SibJZBls4XZzNoitCfpbMYim1M6otaPo8VWaU1Li9cqEiCQlZoE+QbA2F5rtheza3q6aJwraIW1BlGcYaUFXDr/CZcdtk2K4bqG3H8oV7Kr62vunpvAGQ5nurXAayYf6VtrNfdM3NUgcYnUucm9lLTapS101zuk9pLPoPAlK17k290Yes6+knqvbOPLX2xE1031Op2Af0tgfgSVaAdOo4Rhu9xonVeq6ihehCc84QWbt8GsYenrNszhNHq4WhdeaYZzR+ISO/DEcBoe5jA62EqM18U45lJrm5iRC0KxxrDjbY6H/F3sCq/HUY0okCdxYyI7GSkVnoaMPUpjtdrYkU/OcvyOtk07+vgSSx4xlifTve6M2XNlNkCTo4xPLye5rg9GX5N/kuY5D6fOZ4aykanh5gY1x8oazvNg8Czk5RH6/8BsnmxG/gviAk+p0PtbdJEhLdo2EXDH77R0gBvtaOAIGnySFtCY/wdqoq7Suu7ldKc5YedLJnfSpiO1jk1dkE+Xek1ffk6Y/UFEJZ5Iy/jxtYkSHUlbzxrXbz4goD3Ta2GvCNjZcTaJiN1ISo9GU7nmza5P22xnQzva0qY2Io09lwImG4PL1my3hf3t4ki7AOIGbb10CFUt6k7EP1y3rwWJaPpW0Ls/CveeR5hD1+pXpn9OL8iAuG9D9hvgW45CUt4dbwuQ0OD1TmjC47xwfSuR3/6GocQRHHKTUHzgOJw3xuGp8XuvOKUTabFjx2ntIpYXxX1NxKqZXPIN0Zzclf+teIxzPqedyzzoP4d4b4UuppOe0Og9D2PSoSB1LpuLjPzM+iBa3e5bYxpeWNe62O/A9a6X++ZYnPLY115dqpt902gXV9jZTnfdRv3too773xw59b77HVSzrOPfB0/4JQWe74VPvOIDc/iaf27xkI88Fxpv25c+/OtSvrxkxwvjeHHeoJq3OkIcf5WNfV7v7zr9bu8uXaaHJ/S0firrHY5yuKLSk3gCZSdpdHtX7l4svW+6Kms9ylj+3h7Bx02ziJ97sTr/+dCPvvSnT/3qW//62M++9rfP/e57//vgD7/4x0/+8pv//OhPv/rXz/72u//98I+//OdP//rb//74z7///vfP//77v5aSF4DD93+yJ4AGyCEEmBsHuIAImIAFyIAQmBEO+IARWIFeMIFjZYEaeIEYWGwb+IFb0IEeCIIkiHrjd3B1l4IqaFXvh4Ir+IIwaAauh1UuGIM2WHcz6FQ1eIM8KHY5WFQ72INCqE8/qFNBOIRIuE5FaExOdwc7FzON0nnI1IQI94Q6olGYx1FEZzFWOFJRiF/ORIV10IVX+IUmWExiSAdk6IWDIoXHlIZPsoZCZYZLaEtwKAdyOIdtCIbNdIfZloc8gYWxB4RbeG46s20upwwYM1SiJ1Zp6FMKNwocxxuM+DXO94iOg4hb83JnUomhAn2YCHP4pmLd/7CILsWHzBSKZKOJacWJUuKJTfV8qog1rAhn29BShOCGTFiIfiaKrkgLk7gasHhVsqhqsVCLgLgYnwWKxohUkZiM/LGMxeiCNzOK0AiFdpeFJtWM2ZBu1yiMbfeJ05hxxINhwfiN4JiNg2iE3EgKyIiOOyGNl9iOkuiNERVYUCGPmSaBYkOPovCO1YOPgRiOsch8/Gh71AiPvAYYiXiLh0OQxGiQEMFXCamQtSCQYGaPnqCPVLNX/ViRFhlFgdGQy3Bc6tiIHXmQieWPIdkJGGkJAHkJHMl7iJUvKdaS6TOSm3iOAgGRYSVKHomQ5DhjOPkKL1kJMXkLPtlWQKmSNv/JkhYJexGRW21geVJpieRyk9B4lU2QNieJlXzElQWZlVCpkGK5BF5ZlaB3lj95dWUJj2yZlrmwlrMHctrYLVqZjHFJlXNpWaRHXnV4E3kJiHuJN18pjvTmX5cWmJUEkkWJCYXpWUuZVxbHlkzplo65ijypbkpXXKpFC+IFdCJ3l9mVmbS4mQupmHlXBSaplrIWG7poO28pJc/IWHW5dI/1kIepJ4WDijhnmkxTm8zWmf1jXFHjmvxTnGfoK4OJHsLJmarZPp95V5NZdkhHmtvSnLqGmtxGnLBpnLqJnKG2mrX3YcBJNM+ZmiMHa7lJXbvJBdeFnZc1mzqSnt0ZnYf/AmD56ZvUop1reHRntyoKxp/T4p9kCKD/pioDupy6N5RU9piXgKCjiSoLypijR5/XKKE2J6CMxqCiYqBdqKFdoZ9qZKH1AKJWKKKll2AdaqL0gKI9VIosdpt4pzyxyZwYGlLWmG/eWaMldqMNynLlGGK/yKP46aM4BqQfmqNAtaNYo6JI6kUEylxMeoxOqkI0GqXAM6UWVqXOKKNi1qNa6kFKymPnCQ7cKSVQOqaR5qKEdKZRcaUQlqVsel8eKphI1otVVpvWWafM46Y/k6e4o1Rh1qd+aqPleaEQ8YfblW6Geqi+A6jmZBCM+jqFKpqQmqSJeqKCOqQKdal/manA/yapKUOphog/oLqmorqf8hkWaldWe3qOj7qqLYqSq3BHlZqUHReq2lFmsyoah0an14mYpQpHuaqRRiqsXhGsYjo7zHqkxwaWt9png7qTcipnmHoVz7qeabStbreYtroDeHSs9vmk2WoV3voEHJauTlB1EVmsb0SuaYqlvDpovnqu78GucUFy69gLuHqqMIms5lqvfqGvTLCu90qwkxSuNKlprOqgKGWtRbpFv/p4DuuZsZacAcqwHfCaLDJbuooxFeuxETJqGpug0pqSqGanEAur3ziyJ1uyf0myG8qxHECznOOlmQWz4ymlMxuzNZuyDftqLCukRJmh+KpnPYuxd/+HsyvaqpW5tOyls4vFsxernBkrtdFKrBL5BbUaPQD7n0lrl17Lns16W2ULrkLbsUCbs3Aqtgq7sSsrnVl7tVs7ll3LrXSLbHpanwIrCwz3cbQqFycHtWMSt3J7I5W6GCH7aHXzboNLuAJnuIl5tu4qHourF437CYEbRJG7FoXbrzc7tmTLIZmbF5vraTUHuZ/rFaFrs/NCurS3JKdbFqnrkh7nua3bFa+7tqOLuCiruGG7GrfLarlbQ7vLu5MrurELvBNqusPbGcWLPKsrbcl7Fb3LtTihcmF5hH1rixPbNMfLQqX7rVOkt7iJvjKRvXi7vfRSuVLAhRILpsnarKz/u7CWC5jqe7ftSkPLC7tt9r4rB7b2Nr8OWb/QanLWO7sJHLz9q7bmm0TOZqIX170t64Qbd627+rgLjL8N/Lz7CsHq6r8TvKn7qCrw2yFPlMHhmzOdi7wMvL+J+8DnK8Mowb7vekrcuze8WK2tOK/88cLkG8MRnL4jLMI0LMHCRsEFZ8FGy0MsTL8Dy8HeRsRHXMNXHHFFvL7/67t20cQ87L0+DL5SHJytAaWSd7ntu3diDFGdhsaRp8Y5LHc97MaOBseQJ8dtWS5+GL1mhceLp8eXycd1fFFQl79I/HeCTJmYecGm8ISArHiLjC59/L38FcmJN8lj9KpJOE8VC6ll/5pfi9rJPii7yRvKA9appEyEpry7qMxgqrzKntzKrfvK5jnKsrxPn3yotvybuJzL9LTLftrL/cnJwGxGwlynxFygxnzMEJPMbLrMVOp4JVjN3kR5PWfN2nxN2Hy22/zN0NTNH6xJ4FzObyTONoy25rzO+4LOW1xp7BzPFQxLgifP9izAxoevVonISku5Y+C88HyXqmfCYKfPdDnOI0qqTWGZjBzGlcel7jLQ8smWTAHQqfVHEs28nGTQfqmsAe3FqmDRFLZ5HK3QzCzSFrtnFI2nHn3Rh5TRAEwzMA3S1YbQT+vPYoDS5EzSKC3NjJN8X1x8qXR8zdt8+SzUuMdKS4hK1KgB1HvE1CgA1XWxfCdseLIk1QSH1EOH1VFr1PTs1FEN1lM9gCpr1Uft1Um9JkGK1kOr1lct1lnN1sLH1Ycr16RE103p1mYK112t1CL414Ad2II92IRd2IZ92Iid2Iq92Izd2I792JAd2ZI92ZRd2ZZ92Zid2Zq92Zzd2Z792aAd2qJNAgkAACH5BAkHAAcALAAAAAD0AV4BQAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPSBxgyWw6n9CodEqtWq/YrHbL7XqvybCYZh2Yz+i0es1uu9/wuHxOr9vv+Ly8Ou77P2V6goOEhYaHiIlufH+Nf1iKcAGTlJWWl5iZmpucnZ6foKGio5+Ri2COqTyQpm2kr7CxsrO0tZatbFiquzmshbbAwQEExMXGx8LHygTCzZ2GurzSZFeHztevy9rJ2sXY35XQqNPkL75u3N3G4Ozt7u+bcNHl9CvnrsHqyPD8wF9R/UDJGzfin5N6Q+6xCciwYSyDTzzpWxdsoBUTEJkgFKJw/43DjyADVBFAsqTJkyhTksTSzyKjClhUypwpYN5Gc9UkhdzZbyTNnyhZ8nNJ5UJMoEht3mzRUQ3PbxOZBfSJtGrSKsKIQrTKdabSpfZyvnmKLSpDql3TnrSS9U3GJmrjriQINmygsWTzfkMrt6/Mt4CX+B38l27dFE3T6F3sjC/hx4EBP55c8uvhE4nRMJzIWJhjyqBDix790/JlBZGrxOkMLyoxUEdJy55Nu3bXlxtTU1nN2p1rqZ5i2x5OvLhs3D4iQxaL7tPv3u6E1zZAvbr161aMU77O3Xp2ysh7KCec+Qyo59DZSafdvf137YTbd3+/fEoN+vBVljeTvj+l9f+zSUYcgPq9BVp4OFGRH037DeCffwSSJuBwEQZlIHhFlVDhbboZxNuDlaAHz4YCFGDiiSimqOKKJi7o4mgsxijjiQgCcsVkHXqoE4iUiPgOiTMGuWJV+A32WV8klijkkibW6EGSRBrWQYNznOcaj6IAyeSSUSqI4RQH3vjTlks6yQJgMlCJCJYNHUlbjnCmhqOUOqAZg5rWsDmVl8bF6eeF5NF5WkFv4WLooYi6JeaLjBZn2qB3FpropJQaCmWjmAZ6EaQoXArUn6CGKuqopJZqJ6f3LWqkqay26uqrUtRnJqpIeFqaoA7Y6hWuqui666Yw8SmrFLQSQWqqoRaL2qj/hIqqLEfMUpOssseKUO2z2Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z01mvvvfjmqy2s/PbrL6j6rvvvwAQX/E/A3d5V6cIMN+ywHrMiTI/CD1ds8cUPRyxxEngaoufHQ1H66MarMDcIyJ34WAtnH4sDLMlBdIwXypqoTAvLerqsMcw2NJgOzjQH3ZBWxPIcs8kL5XOl0EwHRHQURh9NcdJNBx2ZREsD8zQUGL3Fs89VWx0Y1mZVpOjL1noNM9hhQ+dmXELBs/UTRqnq18jost02a28fh5XZp0xIIa/n6r132T0JO+DfWp8N6OBot2v43ov1vWDB/5muRbi5k1Oel+X5YZ55ZZuX23k7QFMO+uisv4h34Uh7FFDqe6/e+u3avQ7WqB96nnLWnfiK+/DE15ThTbzv6DsnNmsivFzycVdk8SZFj53tXRYNxHirTi072bQvj8nzcVnvHfasm1/d9Egenyb6kKumPPPAi58J+WoJXtul+nev/fuKw9Tp7HcN/KWlfwGy26+28qX/pY19+SNY73jUPHBoiUwzop4GlYRBGe0MAwY0ScEmCKIK7kWBKOlgBjdYPBV60H0PhN8CYfik2OXBSohr2wVdqKLsxaqBUQgTBE3CQxZ9sG5D5FDkpmRDPOAwfEzbYRFP5MMgAhEKQgTdFP9VdEQNqQ0GMiMEAWkhQ00ZDFRzWmKdvpgg+VFqjLMoo//O6Kc0djFcgMGYHvc4gBCyUIClI5cf50LHQhrykIjUyNpQCLdEOvKRkHTV1xgZQTU+YJC6a8QgjWfJXAXwbnfc2CYzeQBMBtIPowykHAtDw6hdMlozgCWtrgUCWroSArYEo7OoJUsb7fKWwAymMIdJzGIa85jITKYyl8nMZjrzmdCMpjSnSc1qWvOa2MymNrfJzW5685vg1GYkx0lOOIWzV+VMpzohcs5UrPOd8NRCOx0Rz3raE2rzhNZu+MjPfvoTYq3MZxul8M+CGvSgawilQGNIUIQ69KH8VOhCIxD/xpPBEY6VIqVAKyqIi2JUZKfMJ0f14NExZjSk8xzpDUtqv5N2cqILUKliKGdCWEDRPzoLKEwZIFPN0LR+N9MHyHJqn51KoKfm+WkOVybUlhVCoyJt4h08V9NsNDVnT0VpOwdoU6Cy9KsBmNtBjEpRqSYVGFUFq0kdp1OyctWqS1UrS8XaBLKW1XtOkaterva7uM6CrookwalEaVb+6PVzY+vrTWEB2CV0LSOTxOtMD/sUvtLPr7JoLAAey85FStanlP3RJ9vHlpAF7oOpfCnsPnvW0LZjlQUqrdzYWtRgJZErUMVjYR3k2uiMVi5xe4dmkQhbzamWc7vtrW/BdEUp/7SEtg48KiUPqNVvvTVomFXPbxNIhbacNiN9qq63rkuz7Fpwu37rLuDawL34tTVvyfWdeU/I3PCqt3HfZeDijmu6+FJ1sfT9IetEN2DxJsy/yg2wFQs8sNvlFlzkTXAc0Xs5gjnYwNyKsIQfQuHQWbh1D7YugltzVc8V948ovgp/BTnidwBYaCdOsYxZKdEDs9aw/Xix2Oo74x4Pq7bv0vDPtrG8GPv4yCEmR/Jm9tW0/me6R47yfqM7MVGR0KNOnsQmpcxl6gIZIUtuDlizLBIod/nMoqlxGMKMjzF79X5mBo36qHPb4c3ZAHXG7Xtv0F7SulHMii0xS7cMlDvn+f92hjYy6b4snsD8uKFMvqyOixzn7cz50K1LdIf1zOiSIbCSf25zoNUhV0L/RNM8RjGqBfzoscZS0c2NwpU3HJxK29EgjrL1p4G7Z8TA+tZTmDWtOWFqBj1uf7o+9hy5Jq1UA/LGvB12KIo9w3/k+tC7bmSnfanoEc6vhG9WsLNTuEUUodlF5UaRml+J6VsNTNjpIbM/zJxuKp47P/VuUq8zkNp/wRs68raFFLd4b3zne92ebLex3/3tBwW8FgOfYsHhk+8CILwB/d63bUPdUeeEe8eWq3gVsRhruJT8CUCp+MV5autPYZjl0K6Sx+eLsogXceQoP7lgdG5ymqhc48T/3bSKV45UHI+6Gyamd75x7oQsLrjVJPf5wYFugYxvewNFj/bRiXw41/ya6fcct7ZXzlmDAJDjh5K2loXu9LArvNpUXiNkIxXzNan960N3+9tpTHVdzt3vaDeU2sss9jfp3epxV1ceIcp4QlB74mbse5Al1fjK4+HxkAfliuW1eMt7ng6YzzyvNx+vzn/+9NB9uuifTXZe7j22h4+97P8V2W7P/va4b1Xt2e7u3Pv+93HafeG9DPzi+wvqdLMm4vG5cd7DnflVfn1KSPlr0pNs+cxu/vA53frttbz3Gq9+9zn//YWjtvx8v/ousJ987ecc2Nk/Zi4H6qdnzZ8D9xdm//7P9MtZ9rKG/WdM+2cXAON60xICA2hXCriADNiADviAEBiBEjiBFFiBFniBGJiBGriBHNiBHviBIBiCIjiCJFiCJniCKJiCKriCLNiCLviCMBiDMjiDNFiDNniDOJiDOriDkmN8PsgvPEgBPziEkhSE0kWESPh/RghzSdiE9beEuOSEUmhOUMhuU3iFgVGFVoiFXGh2WohxXRiGB6ODdYd6ZniGuSB5HFiGaNiGaDh+EMiGbjiHnweHDyiHdJiHjGeHDoiHeviHB8WHDeiHgFiI/SSI4ZR1Mjd4POJSiBhNiigHjIgljqiGibhbiTCJjQhS1rdVmGh3mohTnPiI0P8Uif8WinlRiepnVKbYcKjIGKqYeG71iXnyitARi9A3iLToMbZ4i6NoieCUdf9Fc6MwaZ1BVLLIirsYaVXzcFtHEVjleC/XTcKoVMb4RFzHJsiYiwxYjV1HjKJwjYuxjfGni4T4PW3jjJKWjZSYVZ14Tt6Yjh8HV0jnVNL4jpd4jnn1jeIIPuy4ifdIis8kZDPXj714jKlXjt3YYrOgjgepF8MVhwwpCw75kGQRkXc4kbFQkRbJExjZhxrZVeDYkQiZX8m4UwTpj6RGktHIXtPITSn5jMowbJaVignZfs3SWdcXkvQoaMpVkxd5k66Wk164k9DGkvMGGCpZj/jlkvj/eFc6SVhHiZQQl1jruJJNmYZPGYV/J5WBN1lUKQtAmQkciQkfyVBjaJRfCVphyWFKKZP7kJUJ9ZJMWJReuU/M2JajMJaYUJaXcJYIyEYSE5N6WQl8eQl+eQtCWVde1JWDyZN6KX6yJVyLGVhCiH7pd5LINZWFCWfbR3z3RZkmyY1Q6XzPp5DsQpgPKZmMI5pOeX7SZyFbuS2qeZCsGZruAJil+ZlWkWQ2tpat1ZmWcJtT8FyjiZpcaXuzuS+Q2ZbE6Vym9Zrhh5mwB4wZ1pxhiXcuh5u0oFmhN310aX/YCTIj6RmmGXkAsV5aqWyGt5zZUpv+UZ7BoJ3gl55yqQZ9/4Zs7okt8Nkf8pmUrDZl0HmfaZCf7SmQuzOeH/OfAneeywYF3iWd+uVeq7hawGl0/MiU/ECf5jegtuCd1Dl6CLoU/RlvBlmVvJlmrVkLIBqboGadtKmgesKgKBqgFAqh6jmX2TYavnmdnGmNGjoiDnqgxwdi4WmAeAlowmmYQ8pd/3Jh+ymeP7qkT5aiufNhDDaiyCOjJMmhflakWQqjzDmlVEp4Nsp6YDo6PRqjZEqlXiqiaZo5azqmF6p1ZWqmqpcpBKamR1osJdqTyyA+b7p6LjKn78mltnCiPDKohOphUYqkweaK7KCoIMKojXqlj+qniMpUWJl0Vnqpf2So/P+5qUHVqarTpKAKpVqaG6TakD6pQ6iaqmFaoZtZp5vxqmFjqbJqG6IqpbY6O7haNbq6q0RKqyRqZZI6RmT2ncQaqmJ6BGxGNU02j0zqos16ZquKLKByiuKzrCF6rWiWrc32J9y6PN5qreAqZeL6aqFSrvJFrVU6rOmqqqRZDtGKjix1rvI6r7OKk2CGrHl5Ufoaq1yxannKQgYbdcjnWFLDni8aqQHbl/BKaehqFQn7fil2sU3Hc/rksKAJsUq6CYlZc9+aFhrbcxl7ac85lMnhaOgJaSFbMxPrO8xaPSpLsPlxskzQdv7aaDvam+M5siBTsyWhszsnY0YLADzLsj7/67FK9KtL+Y8eRbQkkbQ9ZrUc27Dg9bKylqyWILQfQ7UCgLVIe7OfmnfIyRT7up1QC5fQ+FVi+7NLq7Adam1Zy65ne6NdG7GIObOeOqxyy3M7i7ZfMLeWSXd5y6utmmBx67Qqim2O+7E9S390i6YgK2oW2bhbq7eVm5leYLgMi7dnyih/GlqaO6H6aTuBC7TGihlrW58wi7mrWbJPi2sCaqWrC3ahe3aj6zqL+5O0y322y7kYW7eFe7e8e7Ck+7u9dbrDq7iQu7kLu1mNqZz+5rW9AbafYHM8xK+E8XOtG3SJ67mv4q6Lob21dlsi571+Ab6ayW/BS0jXy7cA57cN/6q+S8e+feG+9QqA1usv5qsX6Bs8Sldv+ru/Uxe+VRe/nDS/MRuf9lujFLa+B6wW/Ju2TFSxDQzA2MsaA0xsBZxuFRwXFzy53IazG9wvAZwXH7wJ3OtCunu00wu6KCsTJcy0tcTAohqPVxmkwhrC5RbDSstzNDy4Uldv3cd+OIx/y/jAEkujEALEBEe4xfugGwt/ynsSN8yYgqXDdMnDIhvBlSrFEkfFVzzDgivDNpzA7wtCXryfYCyzUNwfL6xCQkzDRazGKrHFhxuYGrzDTSy7ckyp6VHHHXTHRJzGQ3zE6ZbEb0x0gSytPSy1UUTGN2fGNWzFmax5WsTG/ZvB//+rwEdYp4vott5wqvhrwJhsxGiMxZ1Lbkj8rHU5vtUpyrsZux0XtYEKpOqwtkToyibcC4KptvroRHeHwsJbfMC8xMLsmMRMyqBIk8jMuj64zFzcss7Mf8U8VcdMy5xsfNbcxzswWC7QivRrutMsxLIXzrvbtHZZzpFMUt3cu04KztMrznIXlfC8zXYweL48hOxMvfBieobYeGI7r73aXxlR0J530Oma0Cy20AxdeQ4NrhA9LgQ90Q5V0dd60eKS0RodiAw8wuCZqRhNeSG90SNN0sa1rqgC0in9TxzdrB6tWxId0wg108Ra05CixGL40/UkfPRcu0Bd1Pck1FksuUb/vdTrhNSv/LBMHdXl5NRVDKdSfdWRRNVn/M1Y3dWJpNWbPHZevdT33M7S5NP4vIXe3NKy3LEoTH3TbNKl98jTWbE8Pc503bory8zBhNZmfctJncxtjEp53cZ7fc0CWNifDIZ23ac/4NcCfZlrXdK2/Jh/rErxe9f5/NaYPdmyWdkIA9niO9TU3NZa69mLBpukTdQYDEwJ6GsH6H8B6L8FiEyv3Smz3dNKyMS5PUy37bqx/dK7jXXD3dfFTYB/4qu1ncO9rX/HrQLPnaDNrQG/Ddapoa3JDalP6MfBXUzVXb3ZranTDb/jfUvf3cXdrdvlPdrB94Xu/d7wHd/yPd/0L13f9n3f+J3f+r3f/N3f/v3fAB7gAj7gBF7gBn7gCJ7gCr7gDN7gDv7gEB7hyJQAACH5BAkHAAcALAAAAAD0AV4BQAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPSBxgyWw6n9CodEqtWq/YrHbL7XqvybCYZh2Yz+i0es1uu9/wuHxOr9vv+Ly8Ou77P2V6goOEhYaHiIlufH+Nf1iKcAGTlJWWl5iZmpucnZ6foKGio5+Ri2COqTyQpm2kr7CxsrO0tZatbFiquzmshbbAwQEExMXGx8LHygTCzZ2GurzSZFeHztevy9rJ2sXY35XQqNPkL75u3N3G4Ozt7u+bcNHl9CvnrsHqyPD8nl9R/YTJG0fhn5N6qe6xCciwISaDT4DpWxdsoJULEJkgdKRw/43Dj9+qCBhJsqTJkyhHYgGpyWJGACljyhQwb6OPjmpY6gwmcqbPkyt33nrzksnPozQJ2lxVTZLQaxOZ8aSCtOpRKwxdUrXKFWXNpTtwpnkKdaKwnl3TlsQaUOsUtXC/Mi1adIRYNGTzkkILt69MukX9Co4pNyzgl3abvtHLGBTfwZCTHjYYubJkRjCwWP45GWIchmYb+9u6ubTp06iBKm0ROHVawJ9Fu4tKTLPr27hz615LxUTr3ZzpxpbNjjYB28CTK1/+uneQzpSvKkb3yThxWchxG9jOvbt3K8wFex/fHXxlzD+g/0N69wwo69dhZb9Nvr758HDrk78PGT3YCf/qQTFcfB/N59pvuBlI2EuW+fdfBAE+MSCBDSmIGoK3WegVg+c596AL7ZlB4SXw1aIhSQWkqOKKLLboYor4xVjZizTWqKKDH6YQ4gAjWlIiLSeOZOOQLrL3mGBH+hWkkEQ2mSKOOZ6w4xzv0dbjJEs66aSRpPXX5WBLCqBlk1BGaQFsuHh05Vlf5hbhmxBFVpiZIqCZ5lhrTvVWcnD26YWcq9F5pp+B3GnooYWEKeOiXl4kqI6EVoHopJTeoSijmMYV6KMAXrFbpKCGKuqopPbJKQqXNlrqqqy26uqpNqU605wNyBrcFGAl2ZejFdi6IK+wpufnDITCGqlvwwYLRLH/MjDL6bElOKvstNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z0EuHqvfjmi1i94err778Ae8hvsIVWavDBCCdcR5kD01OwwhBHLHGlDDecxJSI5KkxP5TSarEOGE+4MSY/1hJanuIA+/FN0w0yMicl03LymilXvLINO6Yz88s8Z0XUpjcr0fJC+VjZ89Ft/axy0CAPrSbSsgEmkdHAuCUFRvsyDaHTOUEdNV1TRyWQ0jYvUJfWWz9MtNcO6aopW3lZvZ6qAvObM9ttt5lgFT6fwt/eS9N7N96TiK2nFMrBzTHZeqfm8Q3+Jqb204T34zZ+//pmStLjOOsruaROVW554zFmrvlldbPm6af5ijybPhtffvrs4XGO7N8Zth46Pzv3KDvtwANne7QcZmondaJ3YpyvwTc/e9k1mH4r6IslrzxtzOen33e/A78996SrBT01+HJJPfLXG4539mp9X173tLvPHe5Kpv4ooa5nEjPS7DeXEev0SwmGkGQ/QeFvd9bDzurcVDzAwQ91cwMUrto1uCvtTz4LjMmYiOS8DoppgzYa37cq2KMLviJLIKyRB52XwhqJ0FshQ+AmTPi1AJakhTQyH+I6tCcJdg+HL3pht2JYPU/QUDQoBCKLdBiFBoXPfz9UIouEeD/hUCqBnHigE/8DFiEfFnBcxzsUFjehRR5yUT1enOAQ7zWxNt6pfyvE1PAIlsHcnfGOeMzjqyhYxwPp8Y+ADOSb3AXHzQGNAYU0CRX7UMbpXW1QNuTKHFeWSAg+slORhGIUcvXErgQOApWc5MekZY5kPYuUIUAl2hBpysy00oCqBMQrV0nLWtrylrjMpS53ycte+vKXwAymMIdJzGIa85jITKYyl8nMZjrzmdCMpjSnSU1YCvKa2MxaNRmZzW56kwvbfMQ3x0lONYZTDOVMpzqXcM5lnc+N8IynPC31xXaqYHLzzKc+97lIe3oAn/sMqEAl1k9/1opruBijQifRsUMalIiEWKhCG/r/SYMqAKIukygWKVpQaGJUEBrd6KREWc2PUs5rRyRF70ZUs3o+FKF3qFxKR7FSCrXUnBY9KEBjSriZiqKmBLrpJXOq03eCtKdUswVQ4yPUTRLVASSUWVJDSlVKyA0KT4UqTN1TNPVV9atXfUJWi0qF/MXCp19NXlgPMlazbVVEaYWH1JQ6VVqstQlYy8guoxrXkICNrl6txV01AkmI7PWtPOqrO+ZqsrrOYrDsLKxBDrtTrioWSJ2U5BUYA9kspHGodkPsZU2UWasEJW6MixPdcFovvo52FI30yWnJ0tlKWtKpDXPta0MR21ltVi+17ePbOgpG0YousJjtIQCn0Lc2/9iWiaBtrXFlutQTlnYzioNHZ4Xn0Hfp9mjInUVvCUiF5uZCuI7rbvQ8V6fpIhV2h2vi6aRnPPWSD1+fK6sMdyuK8fqxfPO1L7HYm0r38he21+UugDVH0lLmK79TMOuBO+Hf9C64vhWFVCZLoy8Jf6O6xKlwHEcMXdw6WMSe1F0ReQffPKGYxDD+FXE18Fzp4MvD2ABxDXcY4x5/1sQgaiCjwrg2Fqsjdgn2sZL/ImAIfyHARcGx9ZaH3iVbWTczJmsE5WjFFYeUyhu+sphNk2VW/o/BXUbfl7FX5TG7mcMuhVy+Siyg/S4UzC+uivy2E+b47bnPVSmzlOZsY6OedP+Gjj1ajX2yZwMA+nSNfnShgdwD+vq2snA1YqJ7tuiZRDrPyvl0kh2J1ecQmtRSkHLhNs2zTstE1ModMax5vFqxKuuAXp6wq2UcHQYCeoD1Y20V+6TquO5agEL+7wOBvas4fwjXatZ1m+F8Zl8vO9nBji66vksctI5mw1Jc4puXE+4VCfrWBh6Rtyk87Q+WuwDjJve7C3BuOmI6sWtadxbbPW94xzs5/a63sdJNIX2Tkd/z/jfA5y3wU5m0a5oO73WSWG46N2GLtAZTu91d7oaf8t5Uqg6rkYjwd1vcKGaU74+h8JOAO7tfiA15xHWsF4qH++RLwLjKa71zDTL85fb/fok1RC5xTo+a5+vMeLMzDPMoX/GroDZt0m3bYGwR+U5pjXqgp75xnBM2XVdPU9aPrnGug7rqdLrxQNe+XYVD2ePDbhXb5+7crrtdwXC3ptzpznc0HPvu4msyDNnY98L/HfApZjq1Dq9Iszv+8aQipN2RDvnKW/4wkpc0eS/P+c6fjV2M543nR3/GcIZSvTXOe6XJzmSgn17xTHt92VIPdI6wnteUTlue0S5dzeNe21r1/aWBTw6tw/6ikx9+7Uc5S9WZyuGx/Gfzaxn9e04/R9AiQfXRtn1UXf/Z3afx99tK/vKb//zoT7/618/+9rv//fCPv/znT//62//++M+///73z//++///ABiAAjiABFiABniACJiACriADNiADviAEBiBEjiBFEhJpHeBo1KBoISBHJh9GmhmHRiCEfKBwSeCJtgZJKhlJ7iCepWCbsWCMNiCLngAMViDkzWDNGiDOghODwhyhfeDQDgHqvdUPhiERniEaTCERFWESNiEQaiEOcWETjiFfAeFFiWFVJiFA2WFL6VfWviFR8iFzPRweDBhL8NRy1dSMZcxZrgxaChsS7iGQ9eGKDNSgqeGWJhRdEgzdnh850SGPLWHV/KGxHeFcmgIgsiHiMJ7HnWIv5CIPUKIuReHeXhUkGhTfSiGvASIlsU2BgczLaaIg8CIz//EiZnmiSOnUqE4iIlyh9RkiviGikUHCzTXGE1VauQHi9Q1i9mwipHYin5oeo4YbUjziYh2ZBpzi7aWi8OID3hjjJpQi5wFjJpIWYaWB7sojUTXDW5IjWn4igQnC9B4iaLRWebHbarIi+QoiucVjNuEjjSViuv4i35TjboEjz8lj/OIifX4jdOEj6EwjvtIW6lViF14jXgSNto4kOVYkJMYheF4Vvq4Pn9Vh/0Ihw/webgEkFWijpXDWOy4BnOkkbfEkQyJJRUpVR4pCuYoWf9gjV5IjCd5cEWhkL74WA6JiwWhTSUZkTP5ECk5CwJ5CS3ZKzxpSyZ5kiAplBMJCkX/uZMyuJE++ZOWsJTi2JSlkJPLCJWGdY9TSZWUYJUSuZKh8JSY1JW5lJRtaHy/hVoX2Wub54/mopZmyJbZJRTBJXzI5o5g95WXaJd8A1xa2QUrt5WhdW9gGQCAWV6C+ZZblm1yWS50KW2xFpfM1Zh1x2yahJHxMpkHtpiX6ZaZiW2Bx5fb5peQqHVeZ14imXxlZ49xF5PO+F7ICAyqOWkAkTSOyXLLFZnk4pkEQpaOcXtLF5qLs5tPgHe+WVyIeVwLyW6V6UBSwJpqEHqiB5t6J5tF9ozPuW/RaUfGqV2DeXG9yZnwApzxIZyfcJuoFhG6OZrEuZkG6V2ouTHq+W1K/yedudkPbYdlrngu6Hkd9wmd+Qme0/me7cierYedEhA57dWc2ciN8cWbaHZhXGaaQfZgD4qQeAGWCgqZrfI8/+l8+LWh2nloDPmhxekqIoqhJHovThYFxSaIKjpcLPp2y6lhJVpgEPqTNVqa99KiDKp7MGqiEWZn+/ij8rkqQpqj3qehPMqhnTiTSpp4QYqj5jloemmje5drr1Oba1KliMcnI9oB1mlIauel7dCdQiGmY1qe83k7bnpbpTKjXSWhLhafb9qkWSqneqpZaSqTxXGTFDKne2qgfUo8WwqkrmKnNrkMSPadh9pjpCh+i7qkdYqk4MCmO2Gok3ohZcoBmv9ZO2k2m/DAqTrhqZ9KZqG6AaPKHGGXkEaGp2H6p6sqI5WaAa+6HLHaoVWFZ7Z6q1ZWb7uaOKW6nRoFrJIqrIdKrKSJOceKonfGZpfKrFfmrNV2oUKnqVikrAVqrXuKraqFYduqpgrlrT0HrpMqrnA5ZNEKcVSFrhSqrp86pEQaoriZatyaQPKanLoxa+m6QgA7r6/5kL1warJVn0NJctVqFQPrrzD2sE6gczrJMha6oCcKr6A4oIXqmn4hseQZsX8Gmoa5ehf7ezK6r5SwsI1xpgIAsignsvLTsNdpsEJzsnsppae4sagaYh7bFzCbczEWtDCRchVrslfannVmriT/gpW+87PaM7OqOhhES7ElOxdJm7A9yrOEanQ0ixRVO7QjG6wq4aS8AG2meow9u2Nkq7RcAKcBm7PtaplXm50RUmwsyxguW6xG+62qka0Fe7Sxebcq21d7+6ys+muIi6lfN3DEVrjGBrWM+pjKdl18u3WJ+h9oi6yfKbmMuwVwS7AYS7kgWrdp5yeOSlWHC7iVu6yNx7qly1ZB9yapG1KrO6766bevi7uBa7pmsrnSulu3O7cWdm2wu6KCC6D1qRd5a3NSRK+C4XKZO0LLmxfNW3IVB719Ib1x2nQ6G4sl5LQ0+Tv9pr3b+3PTO3hbq27iqwnOq0TmCxfca7PM+b0a/3O94JZw8ZsW85u8c1m9ZIG/5Ku/+8sV/eu7vwnATyHAnVS++QqxlCe0hTmxPnHAsrsuuriNXcu2rstxN/fAFDzBRSvCMSsTFoxXfFSJwdu0HMsS7wtEXme1IRzBIWvC6Nu94JLBM7fBDDvAJgfCNdy7MyzEoosSJ9y46qLD6bO2DPHCOBTDfTvEdBu3JnHEkQV6zci5+tO+Loy9H+y2JUzEYTzFRXwSVgwAmfe9MrfEPNyyXvy8QDzGsRvEZAzBPvdu9lp8WbzCPsLFIOHELQTFIizDZVzFN0y/09KrdtCRTOzGHZy4o0fCV3yaTjcpY/fI1BbJNIzE/1vJiHLJuv97GhgoyWicxO+aUFDXtl5ndqSMwafcCqBMxcXrea1syp4sRqmMydh1gbXcl7dsKLFcyLPceb3MLYEKhm3ksuCaq4tHeMjsRspsrcycyM78zBMTzcw6zejWqNaczJ5bwMqXvmvEzd1MUN8Mzijrv5JZzeWsMNgsrNo8u6vSzub8tehcs+JszOxMzwfzzrcazxsxvDs40OSUxqoczgSd0Nlk0Lq8yQr90KWXwm4K0RR9TQwdyoRc0Rr9Lxcty5m80SANMB0tzB8d0iYtjDRrO7SXz9zU0HE8yWdpqwDtyimNeue8u4isCiSLwCAo060qL7LnbCuNw7bn0mAM0w1603TQytOxp9QqrdRoStSNsNMXHNNGnc5MHTRB3adDndMJ0bZ+uNVSbTFijchdrc7SQNUobJQ17aK9BycD9nwfJ9cxCh2bOH5PCtfQh9eWqtdeSdcnNkh7DdhRKth/7deuRNi/G366ytda7diKith2O4KRbdhpCdnah9kI4YFGGiB3rdgswNixItp5Jdk9Cdr2oNn1wNmFTdk4+NqwHduyPdu0Xdu2fdu4ndu6vdu83du+/dvAHdzCPdzEXdzGfdzIndzKvdzM3dzO/dzQHd3SrQIJAAAh+QQJBwAHACwAAAAA9AFeAUAD/3i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj0gcYMlsOp/QqHRKrVqv2Kx2y+16r8mwmGYdmM/otHrNbrvf8Lh8Tq/b7/i8vDru+z9leoKDhIWGh4iJbnx/jX9YinABk5SVlpeYmZqbnJ2en6ChoqOfkYtgjqk8kKZtpK+wsbKztLWWrWxYqrs5rIW2wMEBBMTFxsfCx8oEws2dhrq80mRXh87Xr8vatl9R2LPQqBXdTtPSvm7J2srf7bXkT+7tcNHj8EvmvOiuwevs8gAxVRFAsKDBgwgTEsQScBS9e0wUSpwooF4+IfvYNNzIsf/SQIogETLs5A9ZsIcQAYRcWVHcxSAZ13Sc2fAjy5sgrcxEaROnz4MWX/6IqYamUXc9fypdWGXnGyxLowYVk1IG0TRHAZYkJixp1K8KU4oFS1bi1DBVY1xFk1XeVmbBvJYtKzbl3LsFz+qAOlfvhrVn2gqOSwWv4cOIE/v0q+TKXcYZAJsZTJmWXMWYM2sGC9kGX7p178WZubWyR8ebU6tevZoRxsusE0oeYJr0WwKfUxvYzbu3byux8foe3hs4XtcwYQc3OLt2x9u4UasmTt34crLUiVvvS8VRXe6B3oCC7hxY7s12W0vPCdEw8j7fQYdP94l8+Xfr0bdXv51i+sf/3QmlQWhUjHafaeclVMCCDDbo4IMQLnjdhJhFaOGFDL4noD0ERmHggZQliBCGJELIUn9kKaeUiCOW6OKCGm7oQnMgbmIffii2+GKJJ6r4k4+L5afQji7GKCMLNNaYyY0BAYlYh1B2CKAVR/ow2xzj3aZkLE4eFuWXY4FnZJUm1IWLRlsaxSKFbOpHJZk7mHlmUWnStGabeCbWGZwjdJnSnIAGKugZYBZqqFh89uAnRIM26qgih0YqaReJtnDnk5NmqummnBLI3puVGnGpl52WauqpqELx6ZihWinkUnuOGuQUR3bZY4Ac2uqfS62+digJmva6gKQzECvsEMaKEOyx/8nC0Oyx0EYr7bTUVmvttdhmq+223Hbr7bfghivuuOSWa+656Kar7rrstuvuu/DGK2+ZqdZr7733zEsuvvz26y+u+k4736MEF2zwwU8BHHCVAyPs8MMQA8rqwlRV02idGA/W6J4UN9awHhmLwmQtpYVMSTigdpzcx3mYnOVb6vjj8iQoT6yyx1V8aMvIM79SV888KSxBWjfnWqAk/WjZsyw/zxw0rRYQXfQESS4dYmHHXeEJz+Ak/F+KvAZctdWC6TrlFFzL8rQUsXHM7thkZ2W2mFI4dUqOmbm9Ltxxq4l1nszdCzhQYaMFkVUWi9f3kiWb9/fgLdkLeV6F7/UqrP+V/5U4fYvL/fjkoE+ut1qXr5j5gJvz0/lRc4fuumajO1v6j/fq3FDjCM7++u68yyb0C7LeWq/tAeFeWfC9J++6zTizLXrqaK4u8m3Iz5XdcHgnf/1vrU/E/A3ZU8i39JxAV7188LCG/NfoR+FdmGCzjFV9SpN/fvzp87foPe79bjj7UoGeTOgHM/IFYFRE4pHyeJdAEn2PYZEingEtozsBNBBDC2TgBS30wG+Nb0tp81kFN2gh4TnvbFHoX/gKQsIIddBbH1RSCEmBwBY+yIQpzNoUVAgkGz7ohd26khxeZrzOdY8z/yoUCqHWMSFKkHEFtN/nVJNEJdKNieiS06D/JsiNCmYwebHLolgcxUUcHfGLgAvjtvYHj4i50WFVjGMW3HU/JMrxjnjk16r8NzUH1PEreQykIE+1Ryz2MQJ/9F3KEOlFnADRD2dUpCGH1siQqHFhiRTJ6YZVSRw+oVZTtOMkGbnCm1xSbJECVqag9awZpfKQVHulslbJLFnK7lewzKUud8nLXvryl8AMpjCHScxiGvOYyEymMpfJzGY685nQjKY0p0nNalrzmtjMZjMHyc1uIkqb7/OmOMe5BXCGk5zoTKf7zPkIdbrznQBgp6+m8MZ62vOed3ikPGd5NHz6858AHYA+9wkC+QX0oAh91EAJ+gAnGqKMEN3ExjbJ/9ADOPQXEc3oLQZ1ymtelBAaDSnNOEpRhn50ECIN6UQXWVE/CrBlBpxhNkoCtEJ01JonjZ70ZEqKImKsZnxsqUVfiocJ8nQUPq0TUEcpVAXkdIDkO+onmlabpUrhAlLzZQxnGsWUUtVka1un0bohzK32tH5eFUtN7/bArPbSrEhFq0i/GrKwQgGrhwsmXFPKtFACUmskkSst7OqFJV5VZXvlKywiqUmdBLartSBsFwwrVoolVrE09GsAHVs+wXaNrfy74mGbSFS2YNYZjDUdFezWhkxijqX6uuxpQ5Fa2q22I5JtQttK2i7Zznaqms1bU3DrtdqakrdvK21gfkuYHf8Gh7MbyW1E1IfclcEDcQZdrlGTyqXgik9woLtp88hKuuxOhouQpaBznye58FYXCG5FknLPu12aNveEkMNX6MQLvrzeMmdIq6/M7ptDNBoYU7C1XCmPm2DNmZc2zHXceg9MYdFWVlGdLORoCzpfCEfYjAWusIj/2uDx4leUQY1Mhz/cxQmP+MWzWugKXEs4ez3RHdz13IJhzGMEb7i8xsXXjduRY9ZluMdIRvFdqbHjlQg5wBspst+anOQqb/bH//WuI2sH5dvZtzY09sn2imPc4IyZN1R2cop7UWbKQmHIGTXfkcFy5t2kGXJ1NsCdNVwOZLXZwm/uMl/lvOev5Ln/0Hk6dJtlbKk/t4+egk4poR09EUVr+XWWdjH+LjwUSitZCnCO6KQvrcP8UbGSAPx0n9uZatUCWHFb82zfwrzl0J66ya2+MqcrlusY95Nzj5Vyz2jNYFOnZn37cTMTzmlrVQca1sH+svSI7cnC6o/UkTP2oz8pLUmF+sM19CGDrLwccTeI0dPwdqRZfJpSmnvc5I7Nu2G0ZjipG9rsvkS4zR1vec8b3dXybWWkClx3z7vaTuBhiLe98CH9u97hEjhlCO6JfYsb4botdcOd/YSQzLsAAKeWxAdD8U5Y3IcYn66yl6BwTSvo4Uw118gFU3JOnNyGKWe5xlW18xM7/N0h/xdYh7FEQGHPzNO7gie1+Quup9Ip1uldHNK9p/SlvzddTp8f1I3usqmbpepz/nqJIz7GhxZd2qvbCrX73fNdz0uLgsq3vsPOduGO/V1wD5TcLbH2ujN8yaT908X2Tom++53jq94btgWf0MbrAez46iMbyeH4yuMB8veS/OIZZfnOzwHz9tK8y8PCec+b/vSDCLrovV5j0Lt+nHwGPDANn/TX256bsee2Xun+2tv7Po+5T/wvaU/13xtfjsFvwjCJHSvekz7m6cZ2zuMZNecH7u6HZH5J+656P49e125vqPUph/3VS7/29eY+xFOx6LaOnynln1orPbAsYc1fvrjcff/+Q1D/Xt1/xrY0fAEICLRkfwPYaPvXVAq4gAzYgA74gBAYgRI4gRRYgRZ4gRiYgRq4gRzYgR74gSAYgiI4giRYgiZ4giiYgiq4gizYgi74gjAYgzI4gzRYgzZ4gziYgzq4g2V1fD5YKjxIAT84hJsShLFEhEiYgEbYAEnYhIWyhKTkhFIYGlAIAVN4hXVRheKHhVxIDlroUl0YhpSigw+GemZ4hmjQfRFYhmjYhqenhhDIhm44h5UHhw8oh3SYhwdlhw6Ih3r4h/jEhw3oh4BYiG4kiNmUdXdAeGSzUoiYTIpoB4xoNY64fvIUiXUwiUtTidDXUphIB5q4VoHCdNL/9IlEF4pgRVLxd4lDhwio6DKciGULaIpD9Ip1pYqPiEy0+G22WFW4aIns9IkChna0wHXlYVXhJ1TCGFOyFgvG6BzIKHuD2Iq86DI1F23LIIqCQIrRtIxR1Yyw8Iy+SAjcCE3euFPgyFXroI2Pd3XgdI5pl45nNWCwaFPuqE3w2DnXmAl0RRnRqHtC6F8C+GAxI45Sp1ZOU1zrF1+7NHNQZJCzhpD1CFqdyAAMqUsOiQn7GDf9+FMKWZGclC/692rARjLyOEEdqVQfKYtWKJBatWJJE3UZlZJpIl34UH0iOXswCQwbSTY0uSU2SX1j9QU9SJAxCZFp0n7QVRlBaXWr/6guGXkJPakkSjlc49ha8LNpINlbO9mLm1CVt9VZMhkLTfl+8JeLIteVXsmP52dJgCWWSKkJZYlokgSMWKeWazl339d7YWkjJykKcxlk9yhzeJmX7eZziNcEW0eMn4WVveZrW5lcRmmYAtGWyccEi0mPthCYlil2aCl0k0mZfNeZWgkFU4kJQblbT3mXoSmahUeaiQkArJULZllsdilGremaBwSbJDYFs7kGxFd8n9lthSmarFeX3kBcFImYdjec0RKV7HacjeWbyumY0kl+zslKxUmZ13l91MkRqUldq4mbJKk6ujkJ3YmddVOdtEmXfBmZipebrpme05cp+zWYcf/ikghYnjr1jYzZV3v5Xe3FXtlZAheZAtAJImO5WLyJGfrlXuOJYTkJPNvZNwsqQgE6IQ9KoLcpodcFZL9mnv6pmSDGcxyKKvcZoaugn/jHn1DFjP/ZXRl6HRuaX/ipYB+aZZCGb+gYoww6o8tRo4NTjtiVoxQqn/oYl2wJpM8FXifKkkhwoCiQoAdyoZnFpIf3RUTKT4KpomDook93nruJpVm6QFvKf7XJdFQaYfRZprBzoyrglCG3pszVpm7qoHCKoGmap05VoXlpp3eqJ3x6AnLaoVsIplonpoAaqKTipQDonq1nqF8aov2pm4vKqG0njSsKqd6ZKtVYkNvgHMH/ialoVKBDxanqiSqfepShCma1SaoiZqqFqqrrhmM+amSoCqsjJqt7amO1SmS3OmWXqqt4wqu5mm2e+qvfoKTfMKrECkaSSoDH+mQ8qhXBaiev+qylGq3016vDo6zYwKzY4KwIkWnMqT11dqxnmYyP2qXfWq1uca0zQa4HYa4bpzz2aqKAtmzeR6a3hqimdVqj5q90lq7DGhX52nGZyq/zdK4CCrDahVkD67CIkbAJd2AWm3ErJ5TWRbE06qeiRj3ZChIZq3JoVLI6t7FEoK4bywSrOqIlQa8GgbIqgbEG26DTya5shrM+RqkvqlgTe6+KQbMURrQLe5MNK7QP67Nh/wq0IsuyJHuzBAs4Rquy/eqxQQqyEBW0+uom2vamuJZs+4q0kJSVvcm0iQqX8tp1I4t+3SCek/e2R8ux8GG24Adq4EoJpwkiMpuqXwC3m9dsf8ewZfuYtrmjJemXVjpsbSuc5AC4TGq4kAmQdSu5aoakGvmXbAu1bvu31xa5Yju4ZFu5oXu3HpK3k7C3B9K36yq3/xq4X5uYzBa7roa2AYuN62hArIusrntsqFa6stsI8VGapwuvmbu4RzdCB6etYPFxpqpKluuWmCuVmmsyN9dCzNu8MAellXJviUt410tC2fsVzsut0XcoL4tZ4btB4xsV5QufMuK9IsqI63tB7f+7FO/LvYkiv5UKvsr7bverFPmrs/YWQahbp//LbwHsEwOsqf5nwMabb/XbQNPXcgrbsik7EQ1MuawJsfQVMqqLngl8cZc7tY7btcSLwi8HdOYbcFp7HyE8pj7ycRU8txastCy0vQRMdtNbIzE8wQlUwxt7wyqsI+b2vEJBp1FWvSY3wihXwlhba1F8uFi7wcLXwbYbsRnzw06Mc1CMw7Y1xfVZsxRhxcrHlT2soExsc12MvV9cxKarsWMrxxJhxoQLlS9cHjEMCm06hBhMt6hEiPOrCXvsqiYcm5D3xzeTj4S8xhjTxz+oyIhFjQdcyLlzyGf7e5IceB4MimdHokb/xLNhbHybbFmUHMGWYMnHI8qTe3uljEmn/L0PubbWy8pSTMpzfMeB3MmniLutOm22TMWunMujS5iMB1KfnLswuwy7S6xnmi15Byia2My6+szYEs1zMs2Nu8DWfC3YfCbazLkLzLv6i3dlt0WTSM2w2s3W8s24EM4HW6bs7MLHrHfpvM0BPM98ErdfYIh1KIbfVDT87AX+7HgAHdCLDLuUV9AJddBSKi8D3QUM3dAOPaGTDLqiMdF7WNFGetFTXM8aHYgc7YXyp9DdENIoXU9I7NFg7LcEndIw/TArjcWYPMojfdMVpM/jsrs43dOjotPiwtM+PdQZCtQ83J1EndRw/+zSDsxLQq3UUE3GntnC8PLUUZ3Ul1nMb4XPyHnVPZ3VgLzVudp8LDvTRyrGb8zBUSjKRt10ZknWYNnU5hDXcj2pNc3Uau3Ub7192WrWrnTXkbrDp1pbbe1Be4196ge/rIbW0huZ2ueob3fYzJPY5bwLdJ3Xh8rWg2rOY83XZU3VRXDZV7zWgE3Ogi3QB9gB/dcq/xenqd2Qr+1grU0msz2lsZ1LtY0Bqx0quU0vSijWhgK9vb0hwy3cT1iUwd0nBfjAv92ixz2Sz42mk6Kdzd2u0T2Qyc2ltw1B1e3a3Y3b263by83a4e3b2f2F6J3e6r3e7N3e7v3e8B3f8j3f9CZd3/Z93/id3/q93/zd3/793wAe4AI+4ARe4AZ+4Aie4Aq+4NqUAAA7';
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/LogTestResultPaletteProvider.js b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/LogTestResultPaletteProvider.js
new file mode 100644 (file)
index 0000000..7254c21
--- /dev/null
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import Cat from '../image';\r
+\r
+\r
+/**\r
+ * A provider for quick service task production\r
+ */\r
+export default function LogTestResultPaletteProvider(palette, create, elementFactory) {\r
+\r
+  this._create = create;\r
+  this._elementFactory = elementFactory;\r
+\r
+  palette.registerProvider(this);\r
+}\r
+\r
+LogTestResultPaletteProvider.$inject = [\r
+  'palette',\r
+  'create',\r
+  'elementFactory'\r
+];\r
+\r
+LogTestResultPaletteProvider.prototype.getPaletteEntries = function() {\r
+\r
+  var elementFactory = this._elementFactory,\r
+      create = this._create;\r
+\r
+  function startCreate(event) {\r
+    var serviceTaskShape = elementFactory.create(\r
+      'shape', { type: 'custom:Log' }\r
+    );\r
+\r
+    create.start(event, serviceTaskShape);\r
+  }\r
+\r
+  return {\r
+    'create-task': {\r
+      group: 'activity',\r
+      title: 'Create a new nyan CAT!',\r
+      imageUrl: Cat.dataURL,\r
+      action: {\r
+        dragstart: startCreate,\r
+        click: startCreate\r
+      }\r
+    }\r
+  };\r
+};
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/index.js b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/index.js
new file mode 100644 (file)
index 0000000..205eb71
--- /dev/null
@@ -0,0 +1,22 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import LogTestResultPaletteProvider from './LogTestResultPaletteProvider';\r
+\r
+export default {\r
+  __init__: [ 'logTestResultPaletteProvider' ],\r
+  logTestResultPaletteProvider: [ 'type', LogTestResultPaletteProvider ]\r
+};
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler-routing.module.ts b/otf-frontend/client/src/app/layout/modeler/modeler-routing.module.ts
new file mode 100644 (file)
index 0000000..5439019
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { ModelerComponent } from './modeler.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+    path: '', component: ModelerComponent\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class ModelerRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler.component.pug b/otf-frontend/client/src/app/layout/modeler/modeler.component.pug
new file mode 100644 (file)
index 0000000..a62cd9d
--- /dev/null
@@ -0,0 +1,162 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#container, style="width:100%;height:100%")\r
+       form(#testDefinitionForm="ngForm")\r
+\r
+               //- Tool Bar\r
+               div#tool-bar\r
+                       input#file(type="file", #file, hidden, (change)="open()")\r
+                       input#fileForVersion(type="file", #fileForVersion, hidden, (change)="newVersion({fromFile: true})")\r
+                       .row.pl-2\r
+                       \r
+                               //- BPMN Diagram items  \r
+\r
+                               .col(style="flex: 0 0 180px;-ms-flex: 0 0 180px;")\r
+                                       .row.no-margin.pl-1\r
+                                               .small.text-muted BPMN Diagram\r
+                                       .row.p-0.pl-2(style="margin-top: -10px")\r
+                                               button(mat-icon-button, [matMenuTriggerFor]="newMenu")\r
+                                                       mat-icon insert_drive_file \r
+                                                               span(style="margin-left: -7px") arrow_drop_down\r
+                                               mat-menu(#newMenu="matMenu")\r
+                                                       button.text-small(mat-menu-item, (click)="newWorkflow()") New Workflow\r
+                                               button(mat-icon-button, matTooltip="Open BPMN", (click)="file.click()")\r
+                                                       mat-icon folder_open\r
+                                               button(mat-icon-button, matTooltip="Download BPMN", (click)="download()")\r
+                                                       mat-icon cloud_download\r
+                                               button(mat-icon-button, matTooltip="View XML", disabled)\r
+                                                       mat-icon code\r
+                                               \r
+\r
+                               mat-divider([vertical]="true")\r
+\r
+                               //- Test Definition items\r
+\r
+                               .col-3()\r
+                                       .row.no-margin.pl-1\r
+                                               .small.text-muted Test Definition\r
+                                       .row.p-0.pl-2(style="margin-top: -10px")\r
+                                               //- Save & Update\r
+                                               button(mat-icon-button, matTooltip="Save Test Definition", [disabled]="inProgress || !testDefinitionForm.form.valid || (hasBeenSaved && !testDefinitionForm.dirty)", (click)="save()")\r
+                                                       mat-icon save\r
+                                               //- Deploy\r
+                                               button(mat-icon-button, matTooltip="Deploy Test Definition", [disabled]="!testDefinitionForm.form.valid || ptd?.currentInstance?.isDeployed || !hasBeenSaved || !testDefinitionForm.pristine || inProgress", (click)="deploy()")\r
+                                                       mat-icon cloud_upload\r
+                                               //- Delete Version\r
+                                               button(mat-icon-button, matTooltip="Delete Version", [disabled]="!hasBeenSaved || inProgress", (click)="deleteVersion()")\r
+                                                       mat-icon delete_forever\r
+\r
+                                               //- Version Select\r
+                                               mat-form-field(*ngIf="ptd", style="width: 80px")\r
+                                                       mat-select(disableOptionCentering, (selectionChange)="setVersion(ptd.currentVersionName)", placeholder="Version", name="selectedVersion", [(ngModel)]="ptd.currentVersionName")\r
+                                                               mat-option(*ngFor="let instance of ptd.bpmnInstances.slice().reverse()", value="{{instance.version}}") {{ instance.version }}\r
+                                               button(mat-icon-button, [matMenuTriggerFor]="versionMenu", matTooltip="New Version")\r
+                                                       mat-icon add\r
+                                               mat-menu(#versionMenu="matMenu")\r
+                                                       button(mat-menu-item, [matMenuTriggerFor]="fromVersion") Create from version\r
+                                                       button(mat-menu-item, (click)="newVersion()") Create blank version\r
+                                                       button(mat-menu-item, (click)="fileForVersion.click()") Create from file\r
+                                               mat-menu(#fromVersion="matMenu")\r
+                                                       button(mat-menu-item, *ngFor="let instance of ptd?.bpmnInstances.slice().reverse(); let i = index", (click)="newVersion({versionIndex: ptd.bpmnInstances.length - i - 1})") {{ instance.version }}\r
+\r
+                                       \r
+               div#left_panel(#modeler)\r
+                       .panel-buttons\r
+                               .properties-panel-button((click)="toggleProperties()") Properties Panel\r
+                               .properties-panel-button((click)="toggleTestDefinition()") Test Definition\r
+               div.properties-panel(#sidebar, [hidden]="!showSidebar")\r
+                       div#properties(#properties, [hidden]="!showProperties", style="width:100%")\r
+                       div(#testDefinition, *ngIf="ptd", [hidden]="!showTestDefinition", style="width:100%;")\r
+                               .col-12\r
+                                       .row.mt-2\r
+                                               .col-12\r
+                                                       \r
+                                                       //- Test Definition Form\r
+\r
+                                                       h4 Details\r
+\r
+                                                       mat-form-field(style="width:100%")\r
+                                                               input(matInput, type="text", placeholder="Name", name="name", [disabled]="(existingTd && !hasBeenSaved)", [(ngModel)]="ptd.testName", required)\r
+                                                               mat-error Required\r
+                                                       mat-form-field(style="width:100%")\r
+                                                               input(matInput, type="text", placeholder="Description", name="description", [disabled]="(existingTd && !hasBeenSaved)", [(ngModel)]="ptd.testDescription", required)\r
+                                                               mat-error Required\r
+                                                       mat-form-field(style="width:100%")\r
+                                                               mat-select((selectionChange)="markFormAs('dirty')", name="ns", [disabled]="(existingTd && !hasBeenSaved)", placeholder="Group", [(value)]="ptd.groupId", required)\r
+                                                                       mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}\r
+                                                               mat-error Required\r
+                                                       //- mat-form-field(style="width:100%")\r
+                                                       //-     input(matInput, type="text", *ngIf="!hasBeenSaved", placeholder="Version", name="version", [(ngModel)]="ptd.currentInstance.version", (keyup)="checkVersionUnique()", required)\r
+                                                       //-     mat-select((selectionChange)="switchVersion(ptd.currentVersionName)", placeholder="Version", name="selectedVersion", *ngIf="hasBeenSaved", [(value)]="ptd.currentVersionName", required)\r
+                                                       //-             mat-option(*ngFor="let instance of ptd.bpmnInstances", value="{{instance.version}}") {{ instance.version }}\r
+                                                       //-     mat-error Required\r
+                                                       //-     button(mat-button, matSuffix, color="primary", *ngIf="hasBeenSaved", (click)="newVersion(this.ptd.processDefinitionKey)", onclick="file.click();") New\r
+\r
+                                       //- .row.mt-2\r
+                                       //-     .col-12\r
+                                       //-             h4 Resources\r
+                                       //-             .text-muted A single .zip file with scripts\r
+                                       //- input(type="file", #scripts, id="scripts", name="scripts", hidden,  (change)="markFormAs('dirty')", ng2FileSelect, [uploader]="uploader", accept="application/zip")\r
+                                       //- .row.mt-1(*ngIf="ptd.currentInstance.resourceFileId")\r
+                                       //-     .col-12\r
+                                       //-             mat-list\r
+                                       //-                     mat-list-item\r
+                                       //-                             mat-icon(mat-list-icon) insert_drive_file\r
+                                       //-                             h4(mat-line) {{ptd.currentInstance.resourceFileName }}\r
+                                       //- .row(*ngIf="!ptd.currentInstance.isDeployed")\r
+                                       //-     .col-md-12\r
+                                       //-             button(mat-raised-button, onclick="scripts.click()", color="primary") \r
+                                       //-                     | {{ !ptd.currentInstance.resourceFileId ? 'Choose File' : 'Replace File' }}\r
+                                                       \r
+                                       //-     .col-md-12\r
+                                       //-             div(*ngIf="uploader?.queue.length > 0")\r
+                                       //-                     label File:\r
+                                       //-                     ul.list-group(style="position:relative")\r
+                                       //-                             li.list-group-item(*ngFor="let item of uploader.queue")\r
+                                       //-                                     | {{ item?.file?.name }}\r
+                                       //-                                     div.upload-progress([ngStyle]="{'width': item.progress + '%'}")\r
+                                       //-                     //- button.pull-right(mat-button, (click)="upload()") Upload All\r
+                                       //-                     label(*ngIf="ptd.currentInstance.resourceFileId && uploader.queue.length > 0 && !saved") This will replace the previous resouce file\r
+                                       //-                     button.pull-right(mat-button, color="primary", (click)="clearQueue()") Remove All\r
+                                       //- .row(*ngIf="ptd.currentInstance.isDeployed")\r
+                                       //-     .col-12(*ngIf="!ptd.currentInstance.resourceFileId")\r
+                                       //-             | No resources were deployed with this version\r
+                                       \r
+                                       .row.mt-3\r
+                                               .col-12(*ngIf="ptd.currentInstance.testDataTemplate != {}")\r
+                                                       h4 testInputTemplate.yaml\r
+                                                       div(style="border: 1px solid lightgrey; font-size: 12px !important")\r
+                                                               codemirror(*ngIf="isRefreshed", [config]="codeConfig", [(ngModel)]="ptd.currentInstance.testDataTemplate", name="testConfig")\r
+\r
+\r
+                       #drag(#handle)\r
+\r
+div(style="position:absolute; bottom: 5px; left: 5px")\r
+       div(*ngIf="inProgress")\r
+               mat-spinner([diameter]="15", style="display:inline")\r
+               div.ml-4(style="display:inline") In Progress\r
+       div(*ngIf="ptd?.currentInstance?.isDeployed") Deployed\r
+       div(*ngIf="hasBeenSaved && !testDefinitionForm.dirty") saved\r
+               mat-icon(style="color:green") check\r
+       \r
+       //- div Form valid: {{ form.valid }}\r
+       //- div Form dirty: {{ form.dirty }}\r
+       //- div hasBeenSaved: {{ hasBeenSaved }}\r
+       //- div(*ngIf="ptd?.currentInstance?.bpmnHasChanged") ptd.currentInstance.bpmnHasChanged\r
+       //- \r
+       //- button((click)="popup()") popup\r
+       //- button((click)="nav()") nav\r
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler.component.scss b/otf-frontend/client/src/app/layout/modeler/modeler.component.scss
new file mode 100644 (file)
index 0000000..61cbddc
--- /dev/null
@@ -0,0 +1,97 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.properties-panel {\r
+  position: absolute;\r
+  top: 0;\r
+  bottom: 0;\r
+  right: 0;\r
+  width: 300px;\r
+  z-index: 10;\r
+  border-left: 1px solid #ccc;\r
+  overflow: auto;\r
+  background: white;\r
+}\r
+\r
+.properties-panel:empty {\r
+  display: none;\r
+}\r
+\r
+.properties-panel > .djs-properties-panel {\r
+  padding-bottom: 70px;\r
+  min-height: 100%;\r
+}\r
+\r
+.properties-toggle {\r
+  position: absolute;\r
+  top: 0;\r
+  right: 280px;\r
+}\r
+\r
+#left_panel {\r
+  position: absolute;\r
+  left: 0;\r
+  top: 40px;\r
+  bottom: 0;\r
+  right: 300px;\r
+}\r
+\r
+#drag {\r
+  position: absolute;\r
+  left: -4px;\r
+  top: 0;\r
+  bottom: 0;\r
+  width: 15px;\r
+  cursor: w-resize;\r
+  z-index: 1000;\r
+}\r
+\r
+.panel-buttons {\r
+  position:absolute; \r
+  right: -103px; \r
+  z-index: 5; \r
+  top: 30%; \r
+  transform: rotate(270deg);\r
+}\r
+\r
+.properties-panel-button {\r
+  cursor: pointer;\r
+  padding: 5px 10px;\r
+  user-select: none;\r
+  background: lightgrey;\r
+  margin-right: 10px;\r
+  display: inline;\r
+}\r
+\r
+#tool-bar {\r
+  position: absolute;\r
+  top: 0px;\r
+  left: 0px;\r
+  width: 100%;\r
+  background: #f8f8f8;\r
+}\r
+\r
+#tool-bar button mat-icon {\r
+  font-size: 18px;\r
+}\r
+\r
+#tool-bar button {\r
+  line-height: 30px !important;\r
+}\r
+\r
+.no-margin{\r
+  margin: 0px !important;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler.component.spec.ts b/otf-frontend/client/src/app/layout/modeler/modeler.component.spec.ts
new file mode 100644 (file)
index 0000000..0c62b8f
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ModelerComponent } from './modeler.component';\r
+\r
+describe('ModelerComponent', () => {\r
+  let component: ModelerComponent;\r
+  let fixture: ComponentFixture<ModelerComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ModelerComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ModelerComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler.component.ts b/otf-frontend/client/src/app/layout/modeler/modeler.component.ts
new file mode 100644 (file)
index 0000000..c090769
--- /dev/null
@@ -0,0 +1,821 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, HostListener } from '@angular/core';\r
+import minimapModule from 'diagram-js-minimap';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { Buffer } from 'buffer';\r
+import propertiesPanelModule from 'bpmn-js-properties-panel';\r
+import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';\r
+import * as camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';\r
+import * as vthTemplate from './templates/elements.json';\r
+import * as $ from 'jquery';\r
+import { MatDialog, MatSnackBar } from '@angular/material';\r
+import { TestHeadService } from 'app/shared/services/test-head.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FileService } from 'app/shared/services/file.service';\r
+import { ActivatedRoute, Router } from '@angular/router';\r
+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';\r
+import { Bpmn } from 'app/shared/models/bpmn.model';\r
+import { TestDefinitionElement, BpmnInstanceElement } from './test-definition-element.class.js';\r
+import { FileUploader } from 'ng2-file-upload';\r
+import { Group } from 'app/shared/models/group.model.js';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';\r
+\r
+interface NewVersionOptions {\r
+  versionIndex: number,\r
+  fromFile: Boolean\r
+}\r
+\r
+@Component({\r
+  selector: 'app-modeler',\r
+  templateUrl: './modeler.component.pug',\r
+  styleUrls: [\r
+    './modeler.component.scss',\r
+  ]\r
+})\r
+\r
+export class ModelerComponent implements OnInit {\r
+\r
+  @ViewChild('container') containerElement: ElementRef;\r
+  @ViewChild('modeler') modelerElement: ElementRef;\r
+  @ViewChild('sidebar') sidebarElement: ElementRef;\r
+  @ViewChild('properties') propertiesElement: ElementRef;\r
+  @ViewChild('handle') handleElement: ElementRef;\r
+\r
+  @ViewChild('testDefinitionForm') form: any;\r
+  @ViewChild('scripts') scripts: ElementRef;\r
+  @ViewChild('file') bpmnFileInput: ElementRef;\r
+\r
+  public qpTestDefinitionId;\r
+\r
+  public ptd: TestDefinitionElement;\r
+  public uploader: FileUploader;\r
+  public bpmnUploader: FileUploader;\r
+  public pStatus: String;\r
+  public inProgress: Boolean;\r
+  public groups: Array<Group>;\r
+  public modeler: Bpmn;\r
+  public showProperties = true;\r
+  public isResizing = false;\r
+  public lastDownX = 0;\r
+  public propertiesWidth = '500px';\r
+  public showSidebar = true;\r
+  public showTestDefinition = false;\r
+  public bpmnId; //javascript input element\r
+  public isRefreshed = false;\r
+  public hasBeenSaved: Boolean = false;\r
+\r
+  constructor(\r
+    public _dialog: MatDialog,\r
+    private _testHeads: TestHeadService,\r
+    private _groups: GroupService,\r
+    private _testDefinitions: TestDefinitionService,\r
+    private _snack: MatSnackBar,\r
+    private _fileTransfer: FileTransferService,\r
+    private _route: ActivatedRoute,\r
+    private _router: Router,\r
+    private _bpmnFactory: BpmnFactoryService) {\r
+  }\r
+\r
+  @HostListener('window:beforeunload', ['$event'])\r
+  canLeavePage($event) {\r
+    $event.preventDefault();\r
+    alert('are you sure')\r
+  }\r
+\r
+  async ngOnInit() {\r
+\r
+    this._route.queryParams.subscribe(res => {\r
+      if (res.testDefinitionId) {\r
+        this.qpTestDefinitionId = res.testDefinitionId;\r
+      } else {\r
+        this.qpTestDefinitionId = null;\r
+      }\r
+      this.setup();\r
+    })\r
+\r
+    //set groups list\r
+    this._groups.find({\r
+      $limit: -1,\r
+      lookup: 'both'\r
+    }).subscribe(res => {\r
+      this.groups = res as Array<Group>;\r
+      this.groups = this._groups.organizeGroups(this.groups);\r
+      \r
+    });\r
+\r
+  }\r
+\r
+  async setup() {\r
+\r
+    this.setInProgress(true);\r
+\r
+    await this.setTestDefinition();\r
+\r
+    const modelerOptions = {\r
+      container: this.modelerElement.nativeElement,\r
+      propertiesPanel: {\r
+        parent: '#properties'\r
+      },\r
+      elementTemplates: [vthTemplate],\r
+      additionalModules: [\r
+        minimapModule,\r
+        propertiesPanelModule,\r
+        propertiesProviderModule\r
+        // colorPickerModule,\r
+        // logTestResultDrawModule,\r
+        // logTestResultPaletteModule\r
+      ],\r
+      moddleExtensions: {\r
+        camunda: camundaModdleDescriptor\r
+      },\r
+      keyboard: {\r
+        bindTo: document\r
+      }\r
+    };\r
+\r
+    // Set up empty modeler\r
+    await this.setModeler({\r
+      mode: 'modeler',\r
+      options: modelerOptions\r
+    });\r
+\r
+    this.setBpmn(false);\r
+\r
+    //set ups draggable properties container\r
+    $(this.handleElement.nativeElement).on('mousedown', e => {\r
+      this.lastDownX = e.clientX;\r
+      this.isResizing = true;\r
+    });\r
+\r
+    $(document).on('mousemove', e => {\r
+      if (!this.isResizing)\r
+        return;\r
+\r
+      var offsetRight = $(this.containerElement.nativeElement).width() - (e.clientX - $(this.containerElement.nativeElement).offset().left);\r
+\r
+      $(this.modelerElement.nativeElement).css('right', offsetRight);\r
+      $(this.sidebarElement.nativeElement).css('width', offsetRight);\r
+    }).on('mouseup', e => {\r
+      this.isResizing = false;\r
+    });\r
+\r
+\r
+  }\r
+\r
+  /*****************************************\r
+   * Form Functionality Methods\r
+   ****************************************/\r
+\r
+  /*** BUTTONS ***/\r
+\r
+  async newWorkflow() {\r
+    if (this.qpTestDefinitionId) {\r
+      this._router.navigate([], {\r
+        queryParams: {}\r
+      });\r
+    } else {\r
+      this.setup();\r
+    }\r
+  }\r
+\r
+  async download() {\r
+    this.modeler.download();\r
+  }\r
+\r
+  async save() {\r
+    this.setInProgress(true);\r
+    let validResult = await this.validateFile();\r
+\r
+    if (validResult) {\r
+      if (this.hasBeenSaved) {\r
+        await this.updateDefinition();\r
+      } else {\r
+        let td = await this.saveDefinition();\r
+        this._router.navigate([], {\r
+          queryParams: {\r
+            testDefinitionId: td['_id']\r
+          }\r
+        });\r
+      }\r
+    }\r
+\r
+    this.snackAlert('Version ' + this.ptd.currentVersionName + ' has been saved');\r
+    this.setInProgress(false);\r
+    this.markFormAs('pristine');\r
+  }\r
+\r
+  async deploy(versionName?) {\r
+    this.inProgress = true;\r
+\r
+    this._testDefinitions.deploy(this.ptd, versionName)\r
+      .subscribe(\r
+        result => {\r
+          this.inProgress = false;\r
+          if (result['statusCode'] == 200) {\r
+            this.snackAlert('Test Definition Deployed Successfully')\r
+            this.ptd.currentInstance.isDeployed = true;\r
+          } else {\r
+            this.errorPopup(result.toString());\r
+          }\r
+        },\r
+        err => {\r
+          this.errorPopup(err.toString());\r
+          this.setInProgress(false);\r
+        }\r
+\r
+      );\r
+  }\r
+\r
+  async deleteVersion() {\r
+    let deleteDialog = this._dialog.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: { type: 'confirmation', message: 'Are you sure you want to delete version ' + this.ptd.currentVersionName }\r
+    });\r
+\r
+    deleteDialog.afterClosed().subscribe(\r
+      result => {\r
+        if (result) {\r
+          this.setInProgress(true);\r
+          if (this.ptd.bpmnInstances.length == 1) {\r
+            this._testDefinitions.delete(this.ptd._id).subscribe(\r
+              result => {\r
+                this.snackAlert('Test definition was deleted');\r
+                this.setInProgress(false);\r
+                this.newWorkflow();\r
+              },\r
+              err => {\r
+                this.setInProgress(false);\r
+                this.errorPopup(err.toString());\r
+              }\r
+            )\r
+          } else {\r
+            let version = this.ptd.currentVersionName;\r
+            // if deleting a version from a definition that has more than 1 version\r
+            this.ptd.removeBpmnInstance(this.ptd.currentVersionName);\r
+\r
+            //prepare patch request\r
+            let request = {\r
+              _id: this.ptd._id,\r
+              bpmnInstances: this.ptd.bpmnInstances\r
+            }\r
+\r
+            this._testDefinitions.patch(request).subscribe(\r
+              res => {\r
+                this.setVersion();\r
+                this.setInProgress(false);\r
+                this.snackAlert('Version ' + version + ' was deleted');\r
+              },\r
+              err => {\r
+                this.setInProgress(false);\r
+                this.errorPopup(err.toString());\r
+              }\r
+            );\r
+          }\r
+        }\r
+      }\r
+    )\r
+  }\r
+\r
+\r
+  /*** UTILITY METHODS ***/\r
+\r
+  //Looks for the definition supplied in the url, or pulls up default workflow\r
+  async setTestDefinition() {\r
+    return new Promise((resolve, reject) => {\r
+      if (this.qpTestDefinitionId) {\r
+        this._testDefinitions.get(this.qpTestDefinitionId).subscribe(\r
+          result => {\r
+            \r
+            this.ptd = new TestDefinitionElement();\r
+            this.ptd.setAll(result);\r
+            this.setAsSaved(true);\r
+            resolve(this.ptd);\r
+          },\r
+          err => {\r
+            this.errorPopup(err.toString());\r
+            reject(err);\r
+          }\r
+        )\r
+      } else {\r
+        //set new test definition\r
+        this.ptd = new TestDefinitionElement();\r
+        resolve(this.ptd);\r
+      }\r
+    });\r
+\r
+  }\r
+\r
+  //will set the selected version. If no version is given, the latest will be selected\r
+  async setVersion(version?) {\r
+\r
+    //if version not supplied, grab latest\r
+    this.ptd.switchVersion(version);\r
+\r
+    this.setBpmn(true);\r
+\r
+  }\r
+\r
+\r
+\r
+  async newVersion(options?: NewVersionOptions) {\r
+\r
+    if (options && options.versionIndex != null) {\r
+\r
+      //create new instance and copy xml\r
+      let instance = this.ptd.newInstance();\r
+      instance.bpmnFileId = this.ptd.bpmnInstances[options.versionIndex].bpmnFileId;\r
+      instance.bpmnXml = this.ptd.bpmnInstances[options.versionIndex].bpmnXml;\r
+\r
+      this.ptd.addBpmnInstance(instance);\r
+\r
+    } else if ( options && options.fromFile) {\r
+      \r
+      let instance = this.ptd.newInstance();\r
+\r
+      instance.bpmnFileId = '0';\r
+      let xml = await new Promise((resolve, reject) => {\r
+        this.fetchFileContents('fileForVersion', xml => {\r
+          resolve(xml);\r
+        });\r
+      });\r
+\r
+      instance.bpmnXml = xml as String;\r
+\r
+      //set the files process definition key\r
+      let parser = new DOMParser();\r
+      let xmlDoc = parser.parseFromString(instance.bpmnXml.toString(), "text/xml");\r
+      //set the process definition key in xml\r
+      xmlDoc.getElementsByTagName("bpmn:process")[0].attributes.getNamedItem("id").value = this.ptd.processDefinitionKey as string;\r
+      //save the xml\r
+      instance.bpmnXml = (new XMLSerializer()).serializeToString(xmlDoc);\r
+\r
+      this.ptd.addBpmnInstance(instance);\r
+\r
+    } else {\r
+      this.ptd.addBpmnInstance();\r
+    }\r
+\r
+    this.setVersion();\r
+    this.markFormAs('dirty');\r
+    this.ptd.currentInstance.bpmnHasChanged = true;\r
+  }\r
+\r
+  popup() {\r
+    \r
+  }\r
+\r
+  async validateFile() {\r
+    return new Promise((resolve, reject) => {\r
+\r
+      this.modeler.getBpmnXml().then(xml => {\r
+\r
+        this.ptd.currentInstance.bpmnXml = xml.toString();\r
+\r
+        this._testDefinitions.validate(this.ptd)\r
+          .subscribe(\r
+            result => {\r
+\r
+              if (result['body'].errors && result['body'].errors != {}) {\r
+                this.errorPopup(JSON.stringify(result['body'].errors));\r
+                resolve(false);\r
+              }\r
+              //this.handleResponse(result, false);\r
+              //this.ptd.currentInstance.bpmnHasChanged = true;\r
+\r
+              // If any VTH or PFLOs were detected, add to object\r
+              // Update list of test heads\r
+              if (result['body']['bpmnVthTaskIds']) {\r
+                this.ptd.currentInstance.testHeads = result['body'].bpmnVthTaskIds;\r
+                this.ptd.currentInstance.testHeads.forEach((elem, val) => {\r
+                  this.ptd.currentInstance.testHeads[val]['testHeadId'] = elem['testHead']._id;\r
+                  delete this.ptd.currentInstance.testHeads[val]['testHead'];\r
+                })\r
+\r
+              }\r
+\r
+              //Update plfos list\r
+              if(result['body']['bpmnPfloTaskIds']){\r
+                this.ptd.currentInstance.pflos = result['body'].bpmnPfloTaskIds;\r
+              }\r
+\r
+              resolve(true);\r
+            },\r
+            err => {\r
+              reject(false);\r
+            }\r
+          );\r
+\r
+      }).catch(err => {\r
+        this.errorPopup(err);\r
+      });\r
+\r
+    });\r
+\r
+  }\r
+\r
+  //returns promise for file object \r
+  async saveBpmnFile() {\r
+    return new Promise((resolve, reject) => {\r
+\r
+      this.modeler.getBpmnXml().then(\r
+        res => {\r
+          this.ptd.currentInstance.bpmnXml = res as String;\r
+          this._testDefinitions.validateSave(this.ptd).subscribe(\r
+            result => {\r
+              resolve(JSON.parse(result.toString())[0]._id);\r
+            },\r
+            err => {\r
+              this.errorPopup(err.toString());\r
+              reject(err);\r
+            }\r
+          )\r
+        }\r
+      )\r
+    });\r
+  }\r
+\r
+  async saveDefinition() {\r
+\r
+    return new Promise(async (resolve, reject) => {\r
+      \r
+      let fileId = await this.saveBpmnFile();\r
+\r
+      if (fileId) {\r
+        this.ptd.currentInstance.bpmnFileId = fileId as String;\r
+      }\r
+\r
+      delete this.ptd._id;\r
+      \r
+      this._testDefinitions.create(this.ptd).subscribe(\r
+        res => {\r
+          \r
+          resolve(res);\r
+        },\r
+        err => {\r
+          this.errorPopup(err.message);\r
+          this.setInProgress(false);\r
+          reject(err);\r
+        }\r
+      )\r
+    });\r
+\r
+  }\r
+\r
+  async updateDefinition() {\r
+    return new Promise(async (resolve, reject) => {\r
+\r
+      let versionIndex = this.ptd.currentVersion;\r
+\r
+      // set parameters to be sent with the patch\r
+      let request = {\r
+        _id: this.ptd._id,\r
+        testName: this.ptd.testName,\r
+        testDescription: this.ptd.testDescription,\r
+        groupId: this.ptd.groupId\r
+      }\r
+\r
+      // If xml has changed, upload file and patch definition details, else just updated details\r
+      if (this.ptd.currentInstance.bpmnHasChanged) {\r
+\r
+        //upload file\r
+        let fileId = await this.saveBpmnFile();\r
+\r
+        //set file id in the bpmn instance\r
+        if (fileId) {\r
+          this.ptd.currentInstance.bpmnFileId = fileId as String;\r
+        }\r
+      }\r
+\r
+      //check if this bpmn version has been saved, else its a new version\r
+      if (this.ptd.currentInstance.createdAt) {\r
+        this.ptd.currentInstance.updatedAt = new Date().toISOString();\r
+        request['bpmnInstances.' + this.ptd.currentVersion] = this.ptd.currentInstance;\r
+      } else {\r
+        this.ptd.currentInstance.createdAt = new Date().toISOString();\r
+        this.ptd.currentInstance.updatedAt = new Date().toISOString();\r
+        request['$push'] = {\r
+          bpmnInstances: this.ptd.currentInstance\r
+        }\r
+      }\r
+\r
+      //patch with updated fields\r
+      this._testDefinitions.patch(request).subscribe(res => {\r
+        this.ptd.currentInstance.bpmnHasChanged = false;\r
+        resolve(res);\r
+      },\r
+        err => {\r
+          reject(err);\r
+        });\r
+    });\r
+  }\r
+\r
+  markFormAs(mode: 'dirty' | 'pristine') {\r
+    if (mode == 'dirty') {\r
+      this.form.control.markAsDirty();\r
+    } else {\r
+      this.form.control.markAsPristine();\r
+    }\r
+  }\r
+\r
+\r
+  async checkProcessDefinitionKey() {\r
+    let foundDefinition = null;\r
+    \r
+    this._testDefinitions.check(this.ptd.processDefinitionKey).subscribe(async result => {\r
+      if (result['statusCode'] == 200) {\r
+        this.pStatus = 'unique';\r
+      } else {\r
+        this.pStatus = 'notUnique';\r
+      }\r
+      \r
+\r
+      //If process definition key found\r
+      if (result['body'] && result['body'][0]) {\r
+\r
+        foundDefinition = result['body'][0];\r
+\r
+      } else {\r
+        //seach mongodb for td with pdk\r
+        await new Promise((resolve, reject) => {\r
+          this._testDefinitions.find({\r
+            processDefinitionKey: this.ptd.processDefinitionKey\r
+          }).subscribe(res => {\r
+            \r
+            if (res['total'] > 0) {\r
+              foundDefinition = res['data'][0];\r
+            }\r
+            resolve()\r
+          }, err => {\r
+            reject();\r
+          })\r
+        });\r
+      }\r
+      \r
+      if (foundDefinition) {\r
+        if (this.qpTestDefinitionId != foundDefinition._id) {\r
+          let confirm = this._dialog.open(AlertModalComponent, {\r
+            width: '400px',\r
+            data: {\r
+              type: 'confirmation',\r
+              message: 'The process definition key "' + this.ptd.processDefinitionKey + '" already exists. Would you like to load the test definition, ' + foundDefinition.testName + ' ? This will delete any unsaved work.'\r
+            }\r
+          });\r
+\r
+          confirm.afterClosed().subscribe(doChange => {\r
+            if (doChange) {\r
+              this._router.navigate([], {\r
+                queryParams: {\r
+                  testDefinitionId: foundDefinition._id\r
+                }\r
+              })\r
+            } else {\r
+              this.bpmnId.value = '';\r
+            }\r
+          })\r
+        }\r
+      } else {\r
+        let tempPK = this.ptd.processDefinitionKey;\r
+        this.ptd.reset();\r
+\r
+        this.ptd.setProcessDefinitionKey(tempPK);\r
+\r
+        this.ptd.setId(null);\r
+        this.ptd.setName('');\r
+        this.ptd.setDescription('');\r
+        this.ptd.setGroupId('');\r
+        this.ptd.setVersion(1);\r
+        this.setAsSaved(false);\r
+      }\r
+\r
+      if (!this.ptd.currentInstance.version) {\r
+        this.ptd.setNewVersion();\r
+      }\r
+\r
+      this.markFormAs('pristine');\r
+\r
+      this.ptd.currentInstance.bpmnHasChanged = false;\r
+\r
+\r
+    });\r
+  }\r
+\r
+  setInProgress(mode: Boolean) {\r
+    this.inProgress = mode;\r
+  }\r
+\r
+  setAsSaved(mode: Boolean) {\r
+    this.hasBeenSaved = mode;\r
+  }\r
+\r
+  /*****************************************\r
+   * BPMN Modeler Functions\r
+   ****************************************/\r
+\r
+  async setBpmn(isNewVersion: Boolean, xml?) {\r
+\r
+    //If a test definition is loaded set bpmnXml with latest version, else set default flow\r
+    if (xml) {\r
+      this.ptd.currentInstance.bpmnXml = xml;\r
+    } else {\r
+      if (this.ptd._id && this.ptd.currentInstance.bpmnFileId) {\r
+        if (!this.ptd.currentInstance.bpmnXml) {\r
+          this.ptd.currentInstance.bpmnXml = await this.getVersionBpmn() as String;\r
+        }\r
+      } else {\r
+        this.ptd.currentInstance.bpmnXml = await this.getDefaultFlow() as String;\r
+\r
+        // If it is a blank new version, set the process definition key in xml\r
+        if (isNewVersion) {\r
+          let parser = new DOMParser();\r
+          //Parse xml\r
+          let xmlDoc = parser.parseFromString(this.ptd.currentInstance.bpmnXml.toString(), "text/xml");\r
+          //set the process definition key in xml\r
+          xmlDoc.getElementsByTagName("bpmn:process")[0].attributes.getNamedItem("id").value = this.ptd.processDefinitionKey as string;\r
+          //save the xml\r
+          this.ptd.currentInstance.bpmnXml = (new XMLSerializer()).serializeToString(xmlDoc);\r
+\r
+        }\r
+      }\r
+    }\r
+\r
+    await this.modeler.setBpmnXml(this.ptd.currentInstance.bpmnXml);\r
+\r
+    //Set bpmn id\r
+    this.bpmnId = (<HTMLInputElement>document.getElementById("camunda-id"));\r
+\r
+    if (!isNewVersion) {\r
+      //Set process Definition key\r
+      this.ptd.processDefinitionKey = this.bpmnId.value;\r
+\r
+      //Check the process Definition key to get its test definition loaded in.\r
+      \r
+      this.checkProcessDefinitionKey();\r
+    }\r
+\r
+    //Listen for any changes made to the diagram and properties panel\r
+    this.modeler.getModel().on('element.changed', (event) => {\r
+      //check to see if process definition key has changed\r
+      if (event.element.type == 'bpmn:Process' && (this.ptd.processDefinitionKey != event.element.id)) {\r
+        this.ptd.processDefinitionKey = event.element.id;\r
+        this.checkProcessDefinitionKey();\r
+      }\r
+\r
+      // If it has been deployed, they cannot edit and save it\r
+      if (!this.ptd.currentInstance.isDeployed) {\r
+        this.ptd.currentInstance.bpmnHasChanged = true;\r
+        this.markFormAs('dirty');\r
+      }\r
+    });\r
+\r
+    this.setInProgress(false);\r
+\r
+  }\r
+\r
+  //Open a .bpmn file\r
+  async open() {\r
+\r
+    this.setInProgress(true);\r
+    this.ptd.reset();\r
+    this.ptd.switchVersion();\r
+\r
+    this.fetchFileContents('file', val => {\r
+      this.setBpmn(false, val);\r
+    });\r
+\r
+  }\r
+\r
+  //Get the xml of the default bpmn file\r
+  async getDefaultFlow() {\r
+    return new Promise((resolve, reject) => {\r
+      this._fileTransfer.get('5d0a5357e6624a3ef0d16164').subscribe(\r
+        data => {\r
+          let bpmn = new Buffer(data as Buffer);\r
+          resolve(bpmn.toString());\r
+        },\r
+        err => {\r
+          this.errorPopup(err.toString());\r
+          reject(err);\r
+        }\r
+      );\r
+    });\r
+  }\r
+\r
+  //set the Modeler\r
+  async setModeler(options) {\r
+    if (!this.modeler) {\r
+      this.modeler = await this._bpmnFactory.setup(options);\r
+    }\r
+  }\r
+\r
+  async getVersionBpmn() {\r
+    return new Promise((resolve, reject) => {\r
+      this._fileTransfer.get(this.ptd.currentInstance.bpmnFileId).subscribe(\r
+        result => {\r
+          let bpmn = new Buffer(result as Buffer);\r
+          resolve(bpmn.toString());\r
+        },\r
+        err => {\r
+          this.errorPopup(err.toString());\r
+          reject(err);\r
+        }\r
+      );\r
+    });\r
+  }\r
+\r
+  fetchFileContents(elementId, callback) {\r
+    var val = "x";\r
+    var fileToLoad = (document.getElementById(elementId))['files'][0];\r
+    var fileReader = new FileReader();\r
+    if (!fileToLoad) {\r
+      return null;\r
+    }\r
+\r
+    fileReader.onload = function (event) {\r
+      val = event.target['result'] as string;\r
+      callback(val);\r
+    }\r
+    fileReader.readAsText(fileToLoad);\r
+  }\r
+\r
+  /*****************************************\r
+   * Page Funtionality Methods\r
+   ****************************************/\r
+\r
+  toggleSidebar(set: Boolean) {\r
+    if (!set) {\r
+      this.showSidebar = false;\r
+      this.modelerElement.nativeElement.style.right = '0px';\r
+    } else {\r
+      this.showSidebar = true;\r
+      $(this.modelerElement.nativeElement).css('right', $(this.sidebarElement.nativeElement).width());\r
+    }\r
+  }\r
+\r
+  toggleProperties() {\r
+    if (!this.showProperties) {\r
+      this.toggleSidebar(true);\r
+      this.showTestDefinition = false;\r
+      this.showProperties = true;\r
+    } else {\r
+      this.toggleSidebar(false);\r
+      this.showProperties = false;\r
+    }\r
+  }\r
+\r
+  toggleTestDefinition() {\r
+    if (!this.showTestDefinition) {\r
+      this.toggleSidebar(true);\r
+      this.showProperties = false;\r
+      this.showTestDefinition = true;\r
+    } else {\r
+      this.toggleSidebar(false);\r
+      this.showTestDefinition = false;\r
+    }\r
+\r
+    this.refresh();\r
+  }\r
+\r
+  refresh() {\r
+    this.isRefreshed = false;\r
+    setTimeout(() => {\r
+      this.isRefreshed = true;\r
+    }, 1);\r
+  }\r
+\r
+  snackAlert(msg) {\r
+    this._snack.openFromComponent(AlertSnackbarComponent, {\r
+      duration: 1500,\r
+      data: {\r
+        message: msg\r
+      }\r
+    });\r
+  }\r
+\r
+  errorPopup(err) {\r
+    return this._dialog.open(AlertModalComponent, {\r
+      width: '400px',\r
+      data: {\r
+        type: 'alert',\r
+        message: err\r
+      }\r
+    });\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler.module.spec.ts b/otf-frontend/client/src/app/layout/modeler/modeler.module.spec.ts
new file mode 100644 (file)
index 0000000..b37984d
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ModelerModule } from './modeler.module';\r
+\r
+describe('ModelerModule', () => {\r
+  let modelerModule: ModelerModule;\r
+\r
+  beforeEach(() => {\r
+    modelerModule = new ModelerModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(modelerModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/modeler/modeler.module.ts b/otf-frontend/client/src/app/layout/modeler/modeler.module.ts
new file mode 100644 (file)
index 0000000..2dee340
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { ModelerRoutingModule } from './modeler-routing.module';\r
+import { ModelerComponent } from './modeler.component';\r
+import { MatButtonModule, MatIconModule, MatTableModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatBadgeModule, MatCardModule, MatSelectModule, MatOptionModule, MatTabsModule, MatProgressSpinnerModule, MatListModule, MatMenuModule, MatTooltipModule, MatDialogModule } from '@angular/material';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { TestHeadModalModule } from 'app/shared/modules/test-head-modal/test-head-modal.module';\r
+import { AlertModalModule } from 'app/shared/modules/alert-modal/alert-modal.module';\r
+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { CodemirrorModule } from 'ng2-codemirror';\r
+import { FileUploadModule } from 'ng2-file-upload';\r
+import { AlertSnackbarModule } from 'app/shared/modules/alert-snackbar/alert-snackbar.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    ModelerRoutingModule,\r
+    MatButtonModule,\r
+    MatIconModule,\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatTableModule,\r
+    MatFormFieldModule,\r
+    MatInputModule,\r
+    MatPaginatorModule,\r
+    TestHeadModalModule,\r
+    AlertModalModule,\r
+    MatBadgeModule,\r
+    PerfectScrollbarModule,\r
+    MatCardModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatIconModule,\r
+    NgbModule,\r
+    CodemirrorModule,\r
+    MatTabsModule,\r
+    MatProgressSpinnerModule,\r
+    FileUploadModule,\r
+    MatListModule,\r
+    MatMenuModule,\r
+    MatTooltipModule,\r
+    AlertSnackbarModule\r
+  ],\r
+  declarations: [ModelerComponent],\r
+})\r
+export class ModelerModule { }\r
diff --git a/otf-frontend/client/src/app/layout/modeler/new.bpmn b/otf-frontend/client/src/app/layout/modeler/new.bpmn
new file mode 100644 (file)
index 0000000..73577a0
--- /dev/null
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_0nye5hw" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.14.0">\r
+  <bpmn:process id="new_diagram" name="new_diagram" isExecutable="true">\r
+    <bpmn:startEvent id="StartEvent_1r2e4pd" camunda:asyncBefore="true">\r
+      <bpmn:outgoing>SequenceFlow_1gpkkbm</bpmn:outgoing>\r
+    </bpmn:startEvent>\r
+    <bpmn:endEvent id="EndEvent_0czvyun">\r
+      <bpmn:incoming>SequenceFlow_1psgifi</bpmn:incoming>\r
+      <bpmn:terminateEventDefinition id="TerminateEventDefinition_12nqmmc" />\r
+    </bpmn:endEvent>\r
+    <bpmn:task id="Task_0e68ysc" name="VTH:PING TEST">\r
+      <bpmn:incoming>SequenceFlow_1d4t09c</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_14b2mg6</bpmn:outgoing>\r
+    </bpmn:task>\r
+    <bpmn:scriptTask id="ScriptTask_1fwzn2i" name="JS Script inline" scriptFormat="JavaScript" camunda:resource="deployment://script.js">\r
+      <bpmn:incoming>SequenceFlow_14b2mg6</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_0i9av57</bpmn:outgoing>\r
+    </bpmn:scriptTask>\r
+    <bpmn:task id="Task_10nhde5" name="UTIL:LogTestResult">\r
+      <bpmn:incoming>SequenceFlow_0i9av57</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_1psgifi</bpmn:outgoing>\r
+    </bpmn:task>\r
+    <bpmn:task id="Task_1r783jz" name="VTH:PING TEST">\r
+      <bpmn:incoming>SequenceFlow_1gpkkbm</bpmn:incoming>\r
+      <bpmn:outgoing>SequenceFlow_1d4t09c</bpmn:outgoing>\r
+    </bpmn:task>\r
+    <bpmn:sequenceFlow id="SequenceFlow_1gpkkbm" sourceRef="StartEvent_1r2e4pd" targetRef="Task_1r783jz" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_1psgifi" sourceRef="Task_10nhde5" targetRef="EndEvent_0czvyun" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_1d4t09c" sourceRef="Task_1r783jz" targetRef="Task_0e68ysc" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_14b2mg6" sourceRef="Task_0e68ysc" targetRef="ScriptTask_1fwzn2i" />\r
+    <bpmn:sequenceFlow id="SequenceFlow_0i9av57" sourceRef="ScriptTask_1fwzn2i" targetRef="Task_10nhde5" />\r
+  </bpmn:process>\r
+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">\r
+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="new_diagram">\r
+      <bpmndi:BPMNShape id="StartEvent_1r2e4pd_di" bpmnElement="StartEvent_1r2e4pd">\r
+        <dc:Bounds x="354" y="117" width="36" height="36" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="416" y="153" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="EndEvent_0czvyun_di" bpmnElement="EndEvent_0czvyun">\r
+        <dc:Bounds x="1189" y="125" width="36" height="36" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="1117" y="165" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="Task_09vptvw_di" bpmnElement="Task_0e68ysc">\r
+        <dc:Bounds x="673" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="ScriptTask_1fwzn2i_di" bpmnElement="ScriptTask_1fwzn2i">\r
+        <dc:Bounds x="845" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="Task_10nhde5_di" bpmnElement="Task_10nhde5">\r
+        <dc:Bounds x="1010" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNShape id="Task_0myfoou_di" bpmnElement="Task_1r783jz">\r
+        <dc:Bounds x="493" y="95" width="100" height="80" />\r
+      </bpmndi:BPMNShape>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_1gpkkbm_di" bpmnElement="SequenceFlow_1gpkkbm">\r
+        <di:waypoint x="390" y="135" />\r
+        <di:waypoint x="493" y="135" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="441.5" y="114" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_1psgifi_di" bpmnElement="SequenceFlow_1psgifi">\r
+        <di:waypoint x="1110" y="135" />\r
+        <di:waypoint x="1150" y="135" />\r
+        <di:waypoint x="1150" y="143" />\r
+        <di:waypoint x="1189" y="143" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="1165" y="133" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_1d4t09c_di" bpmnElement="SequenceFlow_1d4t09c">\r
+        <di:waypoint x="593" y="136" />\r
+        <di:waypoint x="673" y="135" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="633" y="114.5" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_14b2mg6_di" bpmnElement="SequenceFlow_14b2mg6">\r
+        <di:waypoint x="773" y="135" />\r
+        <di:waypoint x="845" y="135" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="809" y="114" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+      <bpmndi:BPMNEdge id="SequenceFlow_0i9av57_di" bpmnElement="SequenceFlow_0i9av57">\r
+        <di:waypoint x="945" y="135" />\r
+        <di:waypoint x="1010" y="135" />\r
+        <bpmndi:BPMNLabel>\r
+          <dc:Bounds x="977.5" y="114" width="0" height="12" />\r
+        </bpmndi:BPMNLabel>\r
+      </bpmndi:BPMNEdge>\r
+    </bpmndi:BPMNPlane>\r
+  </bpmndi:BPMNDiagram>\r
+</bpmn:definitions>\r
diff --git a/otf-frontend/client/src/app/layout/modeler/templates/elements.json b/otf-frontend/client/src/app/layout/modeler/templates/elements.json
new file mode 100644 (file)
index 0000000..5bdd240
--- /dev/null
@@ -0,0 +1,20 @@
+{\r
+  "name": "Virtual Test Head",\r
+  "id": "com.camunda.example.CallTestHeadTask",\r
+  "appliesTo": [\r
+    "bpmn:Task",\r
+    "bpmn:CallActivity"\r
+  ],\r
+  "properties": [\r
+    {\r
+      "label": "Name",\r
+      "type": "String",\r
+      "value": "VTH:",\r
+      "editable": true,\r
+      "binding": {\r
+        "type": "camunda:field",\r
+        "name": "camunda:name"\r
+      }\r
+    }\r
+  ]\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/modeler/test-definition-element.class.ts b/otf-frontend/client/src/app/layout/modeler/test-definition-element.class.ts
new file mode 100644 (file)
index 0000000..06f62da
--- /dev/null
@@ -0,0 +1,203 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestDefinition, BpmnInstance, TestHeadRef, Pflow } from "app/shared/models/test-definition.model";\r
+import { toInteger } from "@ng-bootstrap/ng-bootstrap/util/util";\r
+\r
+export class TestDefinitionElement implements TestDefinition  {\r
+\r
+    testName: String;\r
+    testDescription: String;\r
+    processDefinitionKey: String;\r
+    groupId: String;\r
+    bpmnInstances: BpmnInstanceElement[];\r
+    disabled: Boolean;\r
+    _id: String;\r
+    createdAt: String;\r
+    createdBy: String;\r
+    updatedAt: String;\r
+    updatedBy: String;\r
+\r
+    currentVersion; // int Array index of the bpmnInstances\r
+    currentVersionName;\r
+    currentInstance: BpmnInstanceElement;\r
+\r
+    constructor(testDefinition?){\r
+        if(testDefinition){\r
+            this.setAll(testDefinition);\r
+        }else{\r
+            this.reset();\r
+        }\r
+    }\r
+\r
+\r
+    reset(){\r
+        this._id = "";\r
+        this.testName = '';\r
+        this.testDescription = '';\r
+        this.groupId = '';\r
+        this.processDefinitionKey = '';\r
+        this.disabled = false;\r
+        this.createdAt = null;\r
+        this.createdBy = null;\r
+        this.updatedAt = null;\r
+        this.updatedBy = null;\r
+\r
+        this.bpmnInstances = [];\r
+        this.addBpmnInstance();\r
+\r
+        this.switchVersion();\r
+    }\r
+\r
+    switchVersion(version?){\r
+        if(version){\r
+            //find the version\r
+            this.bpmnInstances.forEach((elem, val) => {\r
+                if(elem['version'] == version){\r
+                    this.currentVersion = val;\r
+                    this.currentInstance = this.bpmnInstances[val];\r
+                    this.currentVersionName = this.currentInstance.version;\r
+                }\r
+            });\r
+        }else{\r
+            //get latest version\r
+            this.currentVersion = this.bpmnInstances.length - 1;\r
+            this.currentInstance = this.bpmnInstances[this.currentVersion];\r
+            this.currentVersionName = this.currentInstance.version;\r
+        }\r
+    }\r
+\r
+    //Setter Methods\r
+\r
+    setAll(td){\r
+        this._id = td._id;\r
+        this.testName = td.testName;\r
+        this.testDescription = td.testDescription;\r
+        this.groupId = td.groupId;\r
+        this.processDefinitionKey = td.processDefinitionKey;\r
+        this.setBpmnInstances(td.bpmnInstances);\r
+        this.switchVersion();\r
+    }\r
+\r
+    setId(id: String){\r
+        this._id = id;\r
+    }\r
+\r
+    setName(testName: String){\r
+        this.testName = testName;\r
+    }\r
+\r
+    setDescription(testDescription: String){\r
+        this.testDescription = testDescription;\r
+    }\r
+\r
+    setGroupId(groupId: String){\r
+        this.groupId = groupId;\r
+    }\r
+\r
+    setProcessDefinitionKey(processDefinitionKey: String){\r
+        this.processDefinitionKey = processDefinitionKey;\r
+    }\r
+\r
+    setBpmnInstances(instances: BpmnInstanceElement[] = []){\r
+        \r
+        \r
+        this.bpmnInstances = instances;\r
+    }\r
+\r
+    setNewVersion(newVersion: number = null){\r
+        if(newVersion == null){\r
+            newVersion = this.bpmnInstances.length;\r
+        }\r
+        if(this.setVersion(newVersion) == -1){\r
+            this.setNewVersion(++newVersion);\r
+        }\r
+        return newVersion;\r
+    }\r
+\r
+    setVersion(version){\r
+        this.bpmnInstances.forEach((elem, val) => {\r
+            if(elem.version == version && this.currentVersion != val ){\r
+                return -1;\r
+            }\r
+        });\r
+        this.currentInstance.version = version;\r
+        return version;\r
+    }\r
+\r
+    addBpmnInstance(instance?){\r
+        if(!instance){\r
+           instance = this.newInstance();\r
+        }\r
+        //console.log(this.bpmnInstances[this.bpmnInstances.length - 1].version )\r
+        if(this.bpmnInstances[this.bpmnInstances.length - 1]){\r
+            instance['version'] = (toInteger(this.bpmnInstances[this.bpmnInstances.length - 1].version) + 1).toString();\r
+        }else{\r
+            instance['version'] = "1";      \r
+        }\r
+        this.bpmnInstances.push(instance);\r
+                \r
+    }\r
+\r
+    removeBpmnInstance(version){\r
+        this.bpmnInstances.forEach((elem, val) =>{\r
+            if(elem['version'] == version){\r
+                this.bpmnInstances.splice(val, 1);\r
+            }\r
+        });\r
+    }\r
+\r
+    getBpmnInstances(version: String = null){\r
+        if(!version)\r
+            return this.bpmnInstances;\r
+        \r
+        this.bpmnInstances.forEach((elem, val) => {\r
+            if(elem['version'] == version){\r
+                return elem;\r
+            }\r
+        });\r
+    }\r
+\r
+    newInstance(): BpmnInstanceElement {\r
+        return {} as BpmnInstanceElement;\r
+    }\r
+\r
+\r
+\r
+\r
+\r
+}\r
+\r
+export class BpmnInstanceElement implements BpmnInstance {\r
+    createdAt: String;\r
+    updatedAt: String;\r
+    processDefinitionId: String;    \r
+    deploymentId: String;\r
+    version: String;\r
+    bpmnFileId: String;\r
+    resourceFileId: String;\r
+    isDeployed: Boolean;\r
+    testHeads: TestHeadRef[];\r
+    pflos: Pflow[];\r
+    testDataTemplate: String;\r
+    testDataTemplateJSON: any;\r
+    updatedBy: String;\r
+    createdBy: String;\r
+\r
+    bpmnXml: String;\r
+    bpmnHasChanged: Boolean;\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test-routing.module.ts b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test-routing.module.ts
new file mode 100644 (file)
index 0000000..cedbf19
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { CreateTestComponent } from './create-test.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+    path:'', component: CreateTestComponent\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class CreateTestRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.pug b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.pug
new file mode 100644 (file)
index 0000000..38be727
--- /dev/null
@@ -0,0 +1,92 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div \r
+  h2 Onboarding\r
+  ol.breadcrumb.bg-light\r
+    li.breadcrumb-item\r
+      a(href="#") Onboarding\r
+    li.breadcrumb-item\r
+      a(href="#") Test Heads\r
+    li.breadcrumb-item.active Test Definition\r
+\r
+  .row([@routerTransition])\r
+    .col-12\r
+      mat-card.mb-5\r
+        mat-card-header.bg-primary.text-white\r
+          mat-card-title\r
+            h4 Create Test Definition\r
+        mat-card-content\r
+          app-create-test-form([listKey]="listKey")\r
+      //.card.border-primary\r
+        .card-header.bg-primary.text-white.text-center\r
+          h4 Create New Tests\r
+        .card-body\r
+          app-create-test-form\r
+\r
+    //.col-sm-4\r
+      h4 Saved Tests\r
+\r
+      input.form-control.bg-light.mb-3([(ngModel)]="search.testName", type="text", placeholder="Search...")\r
+      .list-group\r
+          a.list-group-item.list-group-item-action(*ngFor="let test of test_list | filterBy: search", style="cursor:pointer")\r
+            b {{ test.testName }}\r
+\r
+\r
+//div\r
+  \r
+\r
+  .row\r
+    .col-sm-10\r
+      app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")\r
+      ol.breadcrumb.bg-light\r
+        li.breadcrumb-item\r
+          a(href="#") Onboarding\r
+        li.breadcrumb-item\r
+          a(href="#") Test Heads\r
+        li.breadcrumb-item\r
+          a(href="#") Test Strategies\r
+        li.breadcrumb-item.active Create Test\r
+      \r
+    //  div\r
+        div.ps(style="position: relative; max-width: 600px; max-height: 40px;", [perfectScrollbar]="") \r
+          div hi\r
+\r
+      .footer.row.p-2.bg-light([@routerTransition], style="box-shadow:inset 0px 11px 8px -10px #CCC, inset 0px -11px 8px -10px #CCC; bottom:44px", )\r
+        .col-sm-12\r
+          div(style="width:100%")\r
+            div(style="dispaly:inline-block") Test Strategies\r
+            div(style="display:inline-block") hi\r
+            //input.form-control.col-sm-2(type="text", style="dispaly:inline-block")\r
+        .ps(style="position: relative; max-height: 110px", [perfectScrollbar]="config")\r
+          div.col-sm-12.mb-3(style="white-space: nowrap;")\r
+            .card.mr-3(*ngFor="let vts of vts_list", style="display:inline-block;border-color: #045C87")\r
+              .card-header.text-white(style="background:#045C87") {{ vts.test_strategy_name ? vts.test_strategy_name : vts.test_strategy_id }}\r
+              .card-body\r
+                img(src="assets/images/VNFHealth.PNG", style="width:100px")\r
+  \r
+    //.sidebar-right.col-sm-2.bg-light.p-2(style="box-shadow:inset 11px 0px 8px -10px #CCC, inset -11px 0px 8px -10px #CCC;")\r
+      .ps(style="position: relative;", [perfectScrollbar]="")\r
+        .card.mb-3.border-primary(*ngFor="let vth of vth_list")\r
+          .card-header.text-white.bg-primary {{ vth.test_head_name ? vth.test_head_name : vth.test_head_id }}\r
+          .card-body\r
+            | {{ vth.description }}\r
+\r
+.footer.bg-primary\r
+  .row.p-2\r
+    .col-sm-12\r
+     // button(mat-raised-button, color="primary", (click)="back()") Back\r
+      button.pull-right(mat-raised-button, color="accent", (click)="next()") Next\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.scss b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.scss
new file mode 100644 (file)
index 0000000..7d236e4
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.sidebar-right {\r
+    margin-top: -15px;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.spec.ts b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.spec.ts
new file mode 100644 (file)
index 0000000..ff339f5
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { CreateTestComponent } from './create-test.component';\r
+\r
+describe('CreateTestComponent', () => {\r
+  let component: CreateTestComponent;\r
+  let fixture: ComponentFixture<CreateTestComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ CreateTestComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(CreateTestComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.ts b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.ts
new file mode 100644 (file)
index 0000000..abb9ee6
--- /dev/null
@@ -0,0 +1,73 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Output } from '@angular/core';\r
+import { routerLeftTransition } from '../../../router.animations';\r
+import { ListService } from '../../../shared/services/list.service';\r
+import { HttpClient } from '@angular/common/http';\r
+import { AppGlobals } from '../../../app.global';\r
+import { Router,  NavigationExtras } from '@angular/router';\r
+import { TestDefinitionService } from '../../../shared/services/test-definition.service';\r
+\r
+\r
+@Component({\r
+  selector: 'app-create-test',\r
+  templateUrl: './create-test.component.pug',\r
+  styleUrls: ['./create-test.component.scss', '../onboarding.component.scss'],\r
+  providers: [AppGlobals],\r
+  animations: [routerLeftTransition()]\r
+})\r
+export class CreateTestComponent implements OnInit {\r
+\r
+  public test_list = [];\r
+  public search;\r
+\r
+  @Output() public listKey;\r
+\r
+  constructor(private router: Router, private testDefinition: TestDefinitionService, private list: ListService, private http: HttpClient, private _global: AppGlobals) {\r
+\r
+  }\r
+\r
+  back() {\r
+    this.router.navigateByUrl('/onboarding/test-head');\r
+  }\r
+\r
+  next() {\r
+    let navigationExtras: NavigationExtras = {\r
+      queryParams: {\r
+          "testDefinition": JSON.stringify(this.test_list[this.test_list.length - 1])\r
+      }\r
+    };\r
+    this.router.navigate(['/onboarding/test-instances'], navigationExtras);\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.search = {};\r
+    this.search.testName = "";\r
+\r
+    this.listKey = 'td';\r
+\r
+    //Create List with list service\r
+    this.list.createList(this.listKey);\r
+\r
+    //Subscribe to list service\r
+    this.list.listMap[this.listKey].currentList.subscribe((list) =>{\r
+      this.test_list = list;\r
+    });\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.spec.ts b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.spec.ts
new file mode 100644 (file)
index 0000000..db72c89
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { CreateTestModule } from './create-test.module';\r
+\r
+describe('CreateTestModule', () => {\r
+  let createTestModule: CreateTestModule;\r
+\r
+  beforeEach(() => {\r
+    createTestModule = new CreateTestModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(createTestModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.ts b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.ts
new file mode 100644 (file)
index 0000000..576948d
--- /dev/null
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { CreateTestRoutingModule } from './create-test-routing.module';\r
+import { CreateTestComponent } from './create-test.component';\r
+import { PageHeaderModule } from '../../../shared';\r
+import { PERFECT_SCROLLBAR_CONFIG, PerfectScrollbarConfigInterface } from 'ngx-perfect-scrollbar';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { FormsModule } from '@angular/forms';\r
+import { MatCheckboxModule } from '@angular/material/checkbox';\r
+import { MatRadioModule } from '@angular/material/radio';\r
+import { MatInputModule } from '@angular/material/input';\r
+import { MatIconModule } from '@angular/material/icon';\r
+import { MatButtonModule, MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS, MAT_CHECKBOX_CLICK_ACTION, MatCardModule } from '@angular/material';\r
+import { CreateTestFormModule } from '../../../shared/modules/create-test-form/create-test-form.module';\r
+\r
+const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {\r
+  suppressScrollY: true\r
+};\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    CreateTestRoutingModule,\r
+    CreateTestFormModule,\r
+    PageHeaderModule,\r
+    FilterPipeModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatDialogModule,\r
+    MatCheckboxModule,\r
+    MatRadioModule,\r
+    MatInputModule,\r
+    MatIconModule,\r
+    CreateTestFormModule,\r
+    MatCardModule\r
+  ],\r
+  declarations: [CreateTestComponent],\r
+  providers: [\r
+    {provide: PERFECT_SCROLLBAR_CONFIG, useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG},\r
+    {provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}},\r
+    {provide: MAT_CHECKBOX_CLICK_ACTION, useValue: 'check'}\r
+  ]\r
+})\r
+export class CreateTestModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding-routing.module.ts b/otf-frontend/client/src/app/layout/onboarding/onboarding-routing.module.ts
new file mode 100644 (file)
index 0000000..92dabc3
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { OnboardingComponent } from './onboarding.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+    path: '', component: OnboardingComponent,\r
+    children:[\r
+      { path: '', redirectTo: 'start', pathMatch: 'prefix' },\r
+      { path: 'test-definition', loadChildren: './create-test/create-test.module#CreateTestModule' },\r
+      { path: 'start', loadChildren: './start/start.module#StartModule' },\r
+      { path: 'test-head', loadChildren: './test-head/test-head.module#TestHeadModule' },\r
+      { path: 'test-instances', loadChildren: './test-instances/test-instances.module#TestInstancesModule' }\r
+    ]\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class OnboardingRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.component.pug b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.pug
new file mode 100644 (file)
index 0000000..48d9530
--- /dev/null
@@ -0,0 +1,83 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+router-outlet\r
+//div([@routerTransition])\r
+  app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")\r
+  .row\r
+\r
+    .col-lg-6\r
+  \r
+      .card.mb-12\r
+        .card-header\r
+          | Virtual Test Heads\r
+        .card-body\r
+          .list-group\r
+            a.list-group-item.list-group-item-action(href="#",*ngFor="let vth of vth_list")\r
+              b {{ vth.test_head_id }}\r
+              p {{ vth.description }}\r
+              a(href="{{ vth.url_path }}") {{ vth.url_path }}\r
+\r
+    .col-lg-6\r
+\r
+      .card.bg-light.mb-12\r
+        .card-header\r
+          | New Virtual Test Head\r
+        .card-body\r
+          form(role='form')\r
+            fieldset.form-group\r
+              label Test Head ID\r
+              input.form-control(type="text", )\r
+              p\r
+            \r
+              label Test Head Name\r
+              input.form-control(type="text", )\r
+              p\r
+\r
+              label Description\r
+              input.form-control(type="text",)\r
+              p\r
+\r
+              label Test Head Type\r
+              select.form-control()\r
+                option Proxy\r
+                option Regular\r
+                option Script\r
+                option Adapter\r
+              p\r
+              \r
+              label Implementation Language\r
+              select.form-control()\r
+                option Java\r
+                option Python\r
+                option Javascript/NodeJS \r
+              p\r
+              \r
+              label Base URL\r
+              input.form-control(type="url")\r
+              p\r
+\r
+              label Request Method\r
+              input.form-control(type="url")\r
+              p\r
+\r
+              label Creator\r
+              input.form-control(type="text")\r
+              p\r
+\r
+              button.btn.btn-primary((click)='next($event)') Submit \r
+\r
+ <router-outlet></router-outlet>\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.component.scss b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.scss
new file mode 100644 (file)
index 0000000..4e64050
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.footer {\r
+    position: fixed;\r
+    left: 235px;\r
+    bottom: 0px;\r
+    right: 0px;\r
+    z-index: 100;\r
+}\r
+\r
+@media screen and (max-width: 992px) {\r
+    .footer {\r
+        left: 0px;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.component.spec.ts b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.spec.ts
new file mode 100644 (file)
index 0000000..9268aab
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { OnboardingComponent } from './onboarding.component';\r
+\r
+describe('OnboardingComponent', () => {\r
+  let component: OnboardingComponent;\r
+  let fixture: ComponentFixture<OnboardingComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ OnboardingComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(OnboardingComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.component.ts b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.ts
new file mode 100644 (file)
index 0000000..9c3b288
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { routerLeftTransition } from '../../router.animations';\r
+\r
+@Component({\r
+  selector: 'app-onboarding',\r
+  templateUrl: './onboarding.component.pug',\r
+  styleUrls: ['./onboarding.component.scss'],\r
+  animations: [routerLeftTransition()]\r
+})\r
+export class OnboardingComponent implements OnInit {\r
+\r
+  constructor() {}\r
+  ngOnInit() {}\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.module.spec.ts b/otf-frontend/client/src/app/layout/onboarding/onboarding.module.spec.ts
new file mode 100644 (file)
index 0000000..9a7068e
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { OnboardingModule } from './onboarding.module';\r
+\r
+describe('OnboardingModule', () => {\r
+  let onboardingModule: OnboardingModule;\r
+\r
+  beforeEach(() => {\r
+    onboardingModule = new OnboardingModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(onboardingModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.module.ts b/otf-frontend/client/src/app/layout/onboarding/onboarding.module.ts
new file mode 100644 (file)
index 0000000..3e01611
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { OnboardingRoutingModule } from './onboarding-routing.module';\r
+import { OnboardingComponent } from './onboarding.component';\r
+import { PageHeaderModule } from '../../shared';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    OnboardingRoutingModule,\r
+    PageHeaderModule\r
+  ],\r
+  declarations: [OnboardingComponent]\r
+})\r
+export class OnboardingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/onboarding.service.ts b/otf-frontend/client/src/app/layout/onboarding/onboarding.service.ts
new file mode 100644 (file)
index 0000000..43661d4
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { Observable } from 'rxjs'; \r
+\r
+@Injectable()\r
+export class OnboardingService {\r
+\r
+    private _url: string = 'http://localhost:3000/api/vth;';\r
+\r
+    constructor(private http: HttpClient) {}\r
+\r
+    getVirtualTestHeads() {\r
+        return this.http.get(this._url);\r
+    }\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start-routing.module.ts b/otf-frontend/client/src/app/layout/onboarding/start/start-routing.module.ts
new file mode 100644 (file)
index 0000000..93fe1ce
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { StartComponent } from './start.component';\r
+\r
+const routes: Routes = [{\r
+  path:'', component: StartComponent\r
+}];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class StartRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start.component.pug b/otf-frontend/client/src/app/layout/onboarding/start/start.component.pug
new file mode 100644 (file)
index 0000000..23781db
--- /dev/null
@@ -0,0 +1,24 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.row.fullWidth(style="margin-top:-15px;background-image:url('assets/images/networkBackground.jpg'); background-size:cover; backgroung-repeat:no-repeat; min-height:970px;")\r
+  .col-sm-12\r
+    div.text-center(style="margin-top:15%;background-color: rgba(0,0,0,.5); padding: 30px; border-radius: 5px; max-width: 700px; margin-right:auto; margin-left:auto")\r
+      h1.text-center.text-white Onboarding\r
+\r
+      p.text-center.text-white Welcome to the Open Testing Framework. Click get started below to connect your first test head and create your first test!\r
+      div.text-center\r
+        button(mat-raised-button, color="accent", (click)="next()") Get Started\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start.component.scss b/otf-frontend/client/src/app/layout/onboarding/start/start.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start.component.spec.ts b/otf-frontend/client/src/app/layout/onboarding/start/start.component.spec.ts
new file mode 100644 (file)
index 0000000..b3f164c
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { StartComponent } from './start.component';\r
+\r
+describe('StartComponent', () => {\r
+  let component: StartComponent;\r
+  let fixture: ComponentFixture<StartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ StartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(StartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start.component.ts b/otf-frontend/client/src/app/layout/onboarding/start/start.component.ts
new file mode 100644 (file)
index 0000000..899f8dc
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { Router } from '@angular/router';\r
+\r
+@Component({\r
+  selector: 'app-start',\r
+  templateUrl: './start.component.pug',\r
+  styleUrls: ['./start.component.scss']\r
+})\r
+export class StartComponent implements OnInit {\r
+\r
+  constructor(private router: Router) { }\r
+\r
+  public next(){\r
+    this.router.navigateByUrl('/onboarding/test-head');\r
+  }\r
+  ngOnInit() {\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start.module.spec.ts b/otf-frontend/client/src/app/layout/onboarding/start/start.module.spec.ts
new file mode 100644 (file)
index 0000000..b198e76
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { StartModule } from './start.module';\r
+\r
+describe('StartModule', () => {\r
+  let startModule: StartModule;\r
+\r
+  beforeEach(() => {\r
+    startModule = new StartModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(startModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/start/start.module.ts b/otf-frontend/client/src/app/layout/onboarding/start/start.module.ts
new file mode 100644 (file)
index 0000000..4a236c1
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { StartRoutingModule } from './start-routing.module';\r
+import { StartComponent } from './start.component';\r
+import { MatButtonModule } from '@angular/material';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    StartRoutingModule,\r
+    MatButtonModule\r
+  ],\r
+  declarations: [StartComponent]\r
+})\r
+export class StartModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head-routing.module.ts b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head-routing.module.ts
new file mode 100644 (file)
index 0000000..c166661
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { TestHeadComponent } from './test-head.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+    path:'', component: TestHeadComponent\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class TestHeadRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.pug b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.pug
new file mode 100644 (file)
index 0000000..69ef306
--- /dev/null
@@ -0,0 +1,52 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")\r
+  \r
+  ol.breadcrumb.bg-light\r
+    li.breadcrumb-item\r
+      a(href="#") Onboarding\r
+    li.breadcrumb-item.active Test Heads\r
+\r
+\r
+  .row.mb-5\r
+    .col-sm-8\r
+      mat-card\r
+        mat-card-header.bg-primary.text-white\r
+          mat-card-title \r
+            h4 Create Test Head\r
+        mat-card-content\r
+          app-create-test-head-form([options]="createFormOptions")\r
+      //.card.mb-12.bg-light.border-primary\r
+        .card-header.bg-primary.text-white.text-center\r
+          h4 New Virtual Test Head\r
+        .card-body\r
+          app-create-test-head-form([options]="createFormOptions")\r
+\r
+    .col-sm-4\r
+      h4 Saved Test Heads\r
+\r
+      input.form-control.bg-light.mb-3([(ngModel)]="search.testHeadName", type="text", placeholder="Search...")\r
+      .list-group\r
+          a.list-group-item.list-group-item-action((click)="openTestHead(vth)",*ngFor="let vth of vth_list | filterBy: search", style="cursor:pointer")\r
+            b {{ vth.testHeadName }}\r
+\r
+.footer.bg-primary\r
+  .row.p-2\r
+    .col-sm-12\r
+      //button(mat-raised-button, color="primary", (click)="back()") Back\r
+      //button.pull-right(mat-raised-button, color="accent", (click)="next()") Next\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.scss b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.spec.ts b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.spec.ts
new file mode 100644 (file)
index 0000000..766b121
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestHeadComponent } from './test-head.component';\r
+\r
+describe('TestHeadComponent', () => {\r
+  let component: TestHeadComponent;\r
+  let fixture: ComponentFixture<TestHeadComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestHeadComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestHeadComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.ts b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.ts
new file mode 100644 (file)
index 0000000..136932a
--- /dev/null
@@ -0,0 +1,91 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewContainerRef, Output, Input } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { routerLeftTransition } from '../../../router.animations';\r
+import { Router } from '@angular/router';\r
+import { ListService } from '../../../shared/services/list.service';\r
+import { TestHeadModalComponent } from '../../../shared/modules/test-head-modal/test-head-modal.component';\r
+import { MatDialog } from '@angular/material';\r
+import { TestHeadService } from '../../../shared/services/test-head.service';\r
+\r
+@Component({\r
+  selector: 'app-test-head',\r
+  templateUrl: './test-head.component.pug',\r
+  styleUrls: ['./test-head.component.scss', '../onboarding.component.scss'],\r
+  animations: [routerLeftTransition()]\r
+})\r
+export class TestHeadComponent implements OnInit {\r
+\r
+  public vth_list;\r
+  public search;\r
+\r
+  @Output() public createFormOptions = {\r
+    goal: 'create'  \r
+  }\r
+\r
+  constructor(\r
+    private router: Router, \r
+    private list: ListService,\r
+    public dialog: MatDialog,\r
+    private testHead: TestHeadService\r
+  ) {\r
+    \r
+   }\r
+\r
+  next() {\r
+    this.router.navigateByUrl('/onboarding/test-definition');\r
+  }\r
+\r
+  back() {\r
+    this.router.navigateByUrl('/onboarding');\r
+  }\r
+\r
+  openTestHead(testHead): void {\r
+    const dialogRef = this.dialog.open(TestHeadModalComponent, {\r
+      width: '450px',\r
+      data: {\r
+        goal: 'edit',\r
+        testHead: testHead\r
+      }\r
+    });\r
+\r
+    dialogRef.afterClosed().subscribe(result => {\r
+      \r
+    });\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.search = {};\r
+    this.search._id = "";\r
+    this.search.testHeadName = "";\r
+\r
+    this.list.createList('vth');\r
+    \r
+    this.testHead.find({$limit: -1})\r
+      .subscribe((vth_list) => {\r
+        this.list.changeMessage('vth', vth_list);\r
+      });\r
+\r
+    this.list.listMap['vth'].currentList.subscribe((list) =>{\r
+      this.vth_list = list;\r
+    });\r
+\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.spec.ts b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.spec.ts
new file mode 100644 (file)
index 0000000..a631784
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestHeadModule } from './test-head.module';\r
+\r
+describe('TestHeadModule', () => {\r
+  let testHeadModule: TestHeadModule;\r
+\r
+  beforeEach(() => {\r
+    testHeadModule = new TestHeadModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(testHeadModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.ts b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.ts
new file mode 100644 (file)
index 0000000..96a3f40
--- /dev/null
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule, Pipe } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { TestHeadRoutingModule } from './test-head-routing.module';\r
+import { TestHeadComponent } from './test-head.component';\r
+import { PageHeaderModule, SharedPipesModule } from '../../../shared';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { ListService } from '../../../shared/services/list.service';\r
+import { CreateTestHeadFormModule } from '../../../shared/modules/create-test-head-form/create-test-head-form.module';\r
+import { CreateTestHeadFormComponent } from '../../../shared/modules/create-test-head-form/create-test-head-form.component';\r
+import { TestHeadModalModule } from '../../../shared/modules/test-head-modal/test-head-modal.module';\r
+import { MatButtonModule, MatCardModule } from '@angular/material';\r
+\r
+//import { CreateTestHeadFormComponent } from '../../../shared/modules/create-test-head-form/create-test-head-form.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    TestHeadRoutingModule,\r
+    CreateTestHeadFormModule,\r
+    PageHeaderModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    TestHeadModalModule,\r
+    MatButtonModule,\r
+    MatCardModule\r
+  ],\r
+  declarations: [\r
+    TestHeadComponent\r
+  ]\r
+\r
+})\r
+\r
+export class TestHeadModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances-routing.module.ts b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances-routing.module.ts
new file mode 100644 (file)
index 0000000..93d3967
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { TestInstancesComponent } from './test-instances.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+    path:'', component: TestInstancesComponent\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class TestInstancesRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.pug b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.pug
new file mode 100644 (file)
index 0000000..dcf7bc1
--- /dev/null
@@ -0,0 +1,35 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div\r
+  app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")\r
+  script(src="../../../../../node_modules/codemirror/mode/yaml/yaml.js")\r
+  //- ol.breadcrumb.bg-light\r
+  //-   li.breadcrumb-item\r
+  //-     a(href="#") Onboarding\r
+  //-   li.breadcrumb-item\r
+  //-     a(href="#") Test Heads\r
+  //-   li.breadcrumb-item\r
+  //-     a(href="#") Test Definition\r
+  //-   li.breadcrumb-item.active Test Instances\r
+\r
+  .col-12([@routerTransition])\r
+    app-create-test-instance-form([editInstance] = null)\r
+.footer.bg-primary\r
+  .row.p-2\r
+    .col-sm-12\r
+      //- button(mat-raised-button, color="primary", (click)="back()") Back\r
+      //- button.pull-right(mat-raised-button, color="accent", (click)="next()") Finish\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.scss b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.spec.ts b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.spec.ts
new file mode 100644 (file)
index 0000000..481a5ce
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestInstancesComponent } from './test-instances.component';\r
+\r
+describe('TestInstancesComponent', () => {\r
+  let component: TestInstancesComponent;\r
+  let fixture: ComponentFixture<TestInstancesComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestInstancesComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestInstancesComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.ts b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.ts
new file mode 100644 (file)
index 0000000..0ab8c5a
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Output } from '@angular/core';\r
+import { Router, ActivatedRoute } from '@angular/router';\r
+import { routerLeftTransition } from '../../../router.animations';\r
+import 'codemirror/mode/yaml/yaml.js';\r
+\r
+@Component({\r
+  selector: 'app-test-instances',\r
+  templateUrl: './test-instances.component.pug',\r
+  styleUrls: ['./test-instances.component.scss', '../onboarding.component.scss'],\r
+  animations: [routerLeftTransition()]\r
+})\r
+export class TestInstancesComponent implements OnInit {\r
+  yaml;\r
+\r
+  public codeConfig = {\r
+    mode: "yaml",\r
+    theme: "eclipse",\r
+    lineNumbers: true\r
+  };\r
+\r
+  \r
+  //@Output() public createFormOptions;\r
+  \r
+\r
+  constructor(private router: Router, private route: ActivatedRoute) { }\r
+\r
+  ngOnInit() {\r
+    // this.route.queryParams.subscribe(params => {\r
+    //   this.createFormOptions = params["testDefinition"];\r
+    // });\r
+    \r
+  }\r
+\r
+  // back() {\r
+  //   this.router.navigateByUrl('/onboarding/test-definition');\r
+  // }\r
+\r
+  // next() {\r
+  //   this.router.navigateByUrl('/dashboard');\r
+  // }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.spec.ts b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.spec.ts
new file mode 100644 (file)
index 0000000..f20101d
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestInstancesModule } from './test-instances.module';\r
+\r
+describe('TestInstancesModule', () => {\r
+  let testInstancesModule: TestInstancesModule;\r
+\r
+  beforeEach(() => {\r
+    testInstancesModule = new TestInstancesModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(testInstancesModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.ts b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.ts
new file mode 100644 (file)
index 0000000..c0cd6d1
--- /dev/null
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { TestInstancesRoutingModule } from './test-instances-routing.module';\r
+import { TestInstancesComponent } from './test-instances.component';\r
+import { PageHeaderModule } from '../../../shared';\r
+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { FormsModule } from '@angular/forms';\r
+import { MatButtonModule, MatDialogModule, MatCheckboxModule, MatRadioModule, MatInputModule, MatIconModule, MatExpansionModule, MatCardModule } from '@angular/material';\r
+import { CreateTestFormModule } from '../../../shared/modules/create-test-form/create-test-form.module';\r
+import { CodemirrorModule } from 'ng2-codemirror';\r
+import { CreateTestInstanceFormModule } from '../../../shared/modules/create-test-instance-form/create-test-instance-form.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    PageHeaderModule,\r
+    TestInstancesRoutingModule,\r
+    FilterPipeModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatCheckboxModule,\r
+    MatRadioModule,\r
+    MatInputModule,\r
+    MatIconModule,\r
+    MatExpansionModule,\r
+    CodemirrorModule,\r
+    MatCardModule,\r
+    CreateTestInstanceFormModule\r
+  ],\r
+  declarations: [TestInstancesComponent]\r
+})\r
+export class TestInstancesModule { }\r
diff --git a/otf-frontend/client/src/app/layout/robot-report/robot-report.component.pug b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.pug
new file mode 100644 (file)
index 0000000..9ea8f4b
--- /dev/null
@@ -0,0 +1,24 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+mat-tab-group((selectedTabChange)="refresh()", dinamicHeight, color="", backgroundColor="")\r
+    mat-tab(label="Report", *ngIf="reports && reports.report", active='true')\r
+        iframe(#frame1, width="100%", height='500px', id="frame1", name="frame1", [srcdoc]="reports.report")\r
+    mat-tab(label="Log", *ngIf="reports && reports.log")\r
+        iframe(#frame2, width="100%", height='500px', id="frame2", name="frame2", [srcdoc]="reports.log")\r
+    mat-tab(label="Output", *ngIf="reports && reports.output")\r
+        codemirror(*ngIf="isRefreshed", [config]="codeConfig", [value]="reports.output")\r
+        
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/robot-report/robot-report.component.scss b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/robot-report/robot-report.component.spec.ts b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.spec.ts
new file mode 100644 (file)
index 0000000..8999f4d
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { RobotReportComponent } from './robot-report.component';\r
+\r
+describe('RobotReportComponent', () => {\r
+  let component: RobotReportComponent;\r
+  let fixture: ComponentFixture<RobotReportComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ RobotReportComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(RobotReportComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/robot-report/robot-report.component.ts b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.ts
new file mode 100644 (file)
index 0000000..a790fd6
--- /dev/null
@@ -0,0 +1,82 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, ElementRef, Renderer2, NgModule, Compiler, ViewContainerRef, Inject, Sanitizer } from '@angular/core';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { DomSanitizer } from '@angular/platform-browser'\r
+import * as $ from 'jquery';\r
+import 'codemirror/mode/xml/xml.js';\r
+\r
+\r
+\r
+@Component({\r
+  selector: 'app-robot-report',\r
+  templateUrl: './robot-report.component.pug',\r
+  styleUrls: ['./robot-report.component.scss'],\r
+})\r
+export class RobotReportComponent implements OnInit {\r
+\r
+  @Input() public response;\r
+\r
+  @ViewChild('srcipts') scripts: ElementRef;\r
+  @ViewChild('frame1') frame1: ElementRef;\r
+  @ViewChild('frame2') frame2: ElementRef;\r
+  @ViewChild('codeMirror') codeMirror: ElementRef;\r
+\r
+  @ViewChild('container', {read: ViewContainerRef}) public container;\r
+\r
+  public reports = {\r
+    log: null,\r
+    report: null,\r
+    output: null\r
+  };\r
+\r
+  public codeConfig = {\r
+    mode: "application/xml",\r
+    theme: "eclipse",\r
+    readonly: true,\r
+    lineNumbers: true\r
+  };\r
+\r
+  public isRefreshed = false;\r
+  public noClick = "<script>$(document).ready(function(){ $('div a').removeAttr('href');}); $(document).click(function(){$('div a').removeAttr('href');} )</script>";\r
+\r
+  constructor(private fileTransfer: FileTransferService, private compiler: Compiler, private sanitizer: DomSanitizer) { }\r
+\r
+  ngOnInit() {\r
+    if(this.response){\r
+      if(this.response.vthResponse && this.response.vthResponse.resultData){\r
+        let fileId = this.response.vthResponse.resultData.robotResultFileId;\r
+        if(fileId){\r
+          this.fileTransfer.get(fileId, {robot: true}).subscribe(result => {\r
+            this.reports.log = this.sanitizer.bypassSecurityTrustHtml(result['log.html'] + this.noClick);\r
+            this.reports.report = this.sanitizer.bypassSecurityTrustHtml(result['report.html'] + this.noClick);\r
+            this.reports.output = result['output.xml'];\r
+          });\r
+        }\r
+      }\r
+    }\r
+\r
+  }\r
+\r
+  refresh(){\r
+    this.isRefreshed = false;\r
+    setTimeout(() => {\r
+      this.isRefreshed = true;\r
+    }, 500);\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling-routing.module.ts b/otf-frontend/client/src/app/layout/scheduling/scheduling-routing.module.ts
new file mode 100644 (file)
index 0000000..7d299e3
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { SchedulingComponent } from './scheduling.component';\r
+\r
+const routes: Routes = [{\r
+  path:'', component: SchedulingComponent}];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class SchedulingRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling.component.pug b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.pug
new file mode 100644 (file)
index 0000000..c70c807
--- /dev/null
@@ -0,0 +1,52 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  app-page-header([heading]="'Scheduling'", [icon]="'fa-edit'")\r
+  .card-mb-12\r
+    .card-body\r
+      .row\r
+        div.col-6\r
+          input.form-control.bg-light.mb-1( type="text", placeholder="Search...")\r
+        div.col-6\r
+          button.pull-right.mb-1(mat-raised-button, color="primary", (click)='createSchedule()') Schedule a Test\r
+\r
+      table.mat-elevation-z8.text-center(mat-table, [dataSource]="dataSource", style="width: 100%")\r
+\r
+        ng-container(matColumnDef="name")\r
+          th(mat-header-cell, *matHeaderCellDef) Instance Name\r
+          td(mat-cell, *matCellDef="let element") {{ element.testInstanceName}}\r
+\r
+        ng-container(matColumnDef="description")\r
+          th(mat-header-cell, *matHeaderCellDef)  Date Last Run\r
+          td(mat-cell, *matCellDef="let element") {{ element.lastRunAt }}\r
+\r
+        ng-container(matColumnDef="testDefinition")\r
+          th(mat-header-cell, *matHeaderCellDef) Date Next Run\r
+          td(mat-cell, *matCellDef="let element") {{ element.nextRunAt }}\r
+\r
+        ng-container(matColumnDef="options")\r
+          th(mat-header-cell, *matHeaderCellDef) Options\r
+          td(mat-cell, *matCellDef="let element")\r
+            button.mr-3(mat-mini-fab, aria-label='View', color="primary", (click)='viewSchedule(element)')\r
+              i.fa.fa-eye\r
+            button.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteSchedule(element)')\r
+              i.fa.fa-remove\r
+\r
+        tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+        tr(mat-row, *matRowDef="let row; columns: displayedColumns")\r
+\r
+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling.component.scss b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.scss
new file mode 100644 (file)
index 0000000..b951c73
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.mbtn:focus {\r
+       outline: none;\r
+}\r
+.mat-warn {\r
+    background-color: red;\r
+    color:red;\r
+}\r
+.bg-accent{\r
+    background-color: brown\r
+}\r
+.mat-mini-fab{\r
+    width: 30px !important;\r
+    height: 30px !important;\r
+    line-height: 10px !important;\r
+}\r
+\r
+\r
+tr:nth-child(even){background-color: #f2f2f2;}\r
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling.component.spec.ts b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.spec.ts
new file mode 100644 (file)
index 0000000..26ecee1
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { SchedulingComponent } from './scheduling.component';\r
+\r
+describe('SchedulingComponent', () => {\r
+  let component: SchedulingComponent;\r
+  let fixture: ComponentFixture<SchedulingComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ SchedulingComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(SchedulingComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling.component.ts b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.ts
new file mode 100644 (file)
index 0000000..16e9d6f
--- /dev/null
@@ -0,0 +1,154 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewContainerRef, ViewChild } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { routerLeftTransition } from '../../router.animations';\r
+import { ListService } from '../../shared/services/list.service';\r
+import { Router } from '@angular/router';\r
+import { MatDialog, MatTableDataSource, MatPaginator, MatSnackBar } from '@angular/material';\r
+import { ScheduleTestModalComponent } from '../../shared/modules/schedule-test-modal/schedule-test-modal.component';\r
+import { SchedulingService } from '../../shared/services/scheduling.service';\r
+import { TestInstanceService } from '../../shared/services/test-instance.service';\r
+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';\r
+import { ViewScheduleModalComponent } from '../../shared/modules/view-schedule-modal/view-schedule-modal.component';\r
+import { AlertSnackbarComponent } from '../../shared/modules/alert-snackbar/alert-snackbar.component';\r
+\r
+@Component({\r
+  selector: 'app-scheduling',\r
+  templateUrl: './scheduling.component.pug',\r
+  styleUrls: ['./scheduling.component.scss'],\r
+  animations: [routerLeftTransition()]\r
+})\r
+\r
+export class SchedulingComponent implements OnInit {\r
+\r
+  constructor(private http: HttpClient,\r
+    private router: Router,\r
+    private viewRef: ViewContainerRef,\r
+    private list: ListService,\r
+    private schedulingService: SchedulingService,\r
+    private testInstanceService: TestInstanceService,\r
+    public dialog: MatDialog,\r
+    private snack: MatSnackBar\r
+    ) { }\r
+\r
+  public search;\r
+  public data;\r
+  public dataSource;\r
+  public displayedColumns: string[] = ['name', 'description', 'testDefinition', 'options'];\r
+  public resultsLength;\r
+  public instances;\r
+  public temp;\r
+  public count;\r
+  \r
+  @ViewChild(MatPaginator) paginator: MatPaginator;\r
+\r
+  ngOnInit() {\r
+    this.search = {};\r
+    this.search._id = "";\r
+    this.search.testInstanceName = "";\r
+    this.instances = [];\r
+    this.list.createList('schedules');\r
+    this.temp = {};\r
+    this.count = 0;\r
+\r
+    this.schedulingService.find({$limit: -1, 'data.testSchedule._testInstanceStartDate': { $ne: ['now'] }}).subscribe((list) => {\r
+      \r
+        for(var i = 0; i < Object.keys(list).length; i++){\r
+          list[i].nextRunAt = this.convertDate(list[i].nextRunAt);\r
+          list[i].lastRunAt = this.convertDate(list[i].lastRunAt);\r
+        }\r
+        this.list.changeMessage('schedules', list);\r
+    })\r
+\r
+    \r
+    this.dataSource = new MatTableDataSource();\r
+    this.dataSource.paginator = this.paginator;\r
+    \r
+    this.list.listMap['schedules'].currentList.subscribe((list) =>{\r
+      if(list){\r
+        this.dataSource.data = list;\r
+        this.resultsLength = list.length;\r
+        \r
+       \r
+      }\r
+    });\r
+  }\r
+\r
+  convertDate(str){\r
+    if(!str){\r
+      return 'none'\r
+    }\r
+    str = str.split('-')\r
+    let dayAndTime = str[2];\r
+    let day = dayAndTime.substring(0, dayAndTime.indexOf('T'));\r
+    let time = dayAndTime.substring(dayAndTime.indexOf('T')+1, dayAndTime.length-5);\r
+    return  str[1] + '/' + day + '/' + str[0] + ' at ' + time + ' UTC';\r
+  }\r
+\r
+  viewSchedule(sched){\r
+    this.dialog.open(ViewScheduleModalComponent, {\r
+      width: '450px',\r
+      data: sched\r
+    });\r
+\r
+  }\r
+\r
+  deleteSchedule(sched){\r
+    const dialogRef = this.dialog.open(AlertModalComponent, {\r
+      width : '450px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to delete you schedule? This action cannot be undone.'\r
+      }\r
+    });\r
+    dialogRef.afterClosed().subscribe(result => {\r
+      if(result){\r
+        this.schedulingService.delete(sched._id).subscribe((result) => {\r
+          this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            duration: 1500,\r
+            data: { \r
+              message:'Test Instance Saved'\r
+            }\r
+          });\r
+          this.list.removeElement('sched', '_id', sched._id + '');\r
+          this.list.listMap['sched'].currentList.subscribe(x => {\r
+              this.dataSource = x;\r
+          });\r
+\r
+        })\r
+      }\r
+    })\r
+    \r
+  }\r
+\r
+  createSchedule(){\r
+    const dialogRef = this.dialog.open(ScheduleTestModalComponent, {\r
+      width: '90%'\r
+    });\r
+\r
+    dialogRef.afterClosed().subscribe(result => {\r
+      /*if(result != ''){\r
+        this.test_instance_selected = result;\r
+        this.strategy_selected = true;\r
+      }else{\r
+        this.strategy_selected = false;\r
+      }*/\r
+    });\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling.module.spec.ts b/otf-frontend/client/src/app/layout/scheduling/scheduling.module.spec.ts
new file mode 100644 (file)
index 0000000..b75cb0e
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { SchedulingModule } from './scheduling.module';\r
+\r
+describe('SchedulingModule', () => {\r
+  let schedulingModule: SchedulingModule;\r
+\r
+  beforeEach(() => {\r
+    schedulingModule = new SchedulingModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(schedulingModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/scheduling/scheduling.module.ts b/otf-frontend/client/src/app/layout/scheduling/scheduling.module.ts
new file mode 100644 (file)
index 0000000..00f7e12
--- /dev/null
@@ -0,0 +1,52 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { SchedulingRoutingModule } from './scheduling-routing.module'\r
+import { SchedulingComponent } from './scheduling.component';\r
+import { PageHeaderModule } from '../../shared/modules';\r
+import { MatButtonModule, MatIconModule, MatDatepickerModule, MatCheckboxModule, MatTableModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatSnackBarModule} from '@angular/material';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { ScheduleTestModalModule } from '../../shared/modules/schedule-test-modal/schedule-test-modal.module';\r
+import { AlertModalModule } from '../../shared/modules/alert-modal/alert-modal.module';\r
+import { ViewScheduleModalModule } from '../../shared/modules/view-schedule-modal/view-schedule-modal.module';\r
+\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    SchedulingRoutingModule,\r
+    ViewScheduleModalModule,\r
+    AlertModalModule,\r
+    MatTableModule, \r
+    MatFormFieldModule, \r
+    MatInputModule, \r
+    MatPaginatorModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatCheckboxModule,\r
+    MatDatepickerModule,\r
+    MatFormFieldModule,\r
+    MatIconModule,\r
+    PageHeaderModule,\r
+    MatSnackBarModule,\r
+    ScheduleTestModalModule\r
+  ],\r
+  declarations: [SchedulingComponent]\r
+})\r
+export class SchedulingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/settings/settings-routing.module.ts b/otf-frontend/client/src/app/layout/settings/settings-routing.module.ts
new file mode 100644 (file)
index 0000000..190e894
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { SettingsComponent } from './settings.component';\r
+\r
+const routes: Routes = [\r
+  {\r
+    path: '',\r
+    component: SettingsComponent\r
+  }\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class SettingsRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/settings/settings.component.pug b/otf-frontend/client/src/app/layout/settings/settings.component.pug
new file mode 100644 (file)
index 0000000..2cd086b
--- /dev/null
@@ -0,0 +1,42 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+    h2 Settings\r
+    hr\r
+\r
+h4(style="padding-top:1em;") Default Group Configuration\r
+.row\r
+    .col-sm-5\r
+        label Enable default group setting? \r
+.row\r
+    .col-sm-5\r
+        mat-radio-group\r
+            mat-radio-button(value='enable', style="padding-right:5em;", [checked]="defaultGroupEnabled", (click)="enableDefaultGroup()") Enable\r
+            mat-radio-button(value='disable', [checked]="!defaultGroupEnabled", (click)="disableDefaultGroup()") Disable\r
+.row\r
+    .col-sm-5\r
+        mat-form-field\r
+            mat-label(style="color:black") {{ defaultGroup?.groupName }}\r
+            mat-select([disabled]='!defaultGroupEnabled')\r
+                mat-option(*ngFor="let group of eligibleGroups", (click)='changDefaultGroup(group)') {{group?.groupName}}\r
+.row\r
+    .col-sm-5\r
+        // button for submitting the information in the form\r
+        button(mat-raised-button='', color='primary', class='pull-right', (click)='update()') Save\r
+\r
+hr\r
+        
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/settings/settings.component.scss b/otf-frontend/client/src/app/layout/settings/settings.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/settings/settings.component.spec.ts b/otf-frontend/client/src/app/layout/settings/settings.component.spec.ts
new file mode 100644 (file)
index 0000000..a86c4e4
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { SettingsComponent } from './settings.component';\r
+\r
+describe('SettingsComponent', () => {\r
+  let component: SettingsComponent;\r
+  let fixture: ComponentFixture<SettingsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ SettingsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(SettingsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/settings/settings.component.ts b/otf-frontend/client/src/app/layout/settings/settings.component.ts
new file mode 100644 (file)
index 0000000..16312b7
--- /dev/null
@@ -0,0 +1,103 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { routerTransition } from 'app/router.animations';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { Group } from 'app/shared/models/group.model';\r
+import { MatSnackBar } from '@angular/material';\r
+\r
+@Component({\r
+  selector: 'app-settings',\r
+  templateUrl: './settings.component.pug',\r
+  styleUrls: ['./settings.component.scss'],\r
+  animations: [routerTransition()]\r
+})\r
+\r
+export class SettingsComponent implements OnInit {\r
+  defaultGroupEnabled = false;\r
+  private defaultGroup;\r
+  private eligibleGroups;\r
+  private currentUser;\r
+  private defaultGroupId;\r
+\r
+  constructor(private cookie: CookieService,\r
+    private user: UserService,\r
+    private _group: GroupService,\r
+    private snack: MatSnackBar\r
+  ) { }\r
+\r
+  ngOnInit() {\r
+\r
+    this.currentUser = JSON.parse(this.cookie.get('currentUser'));\r
+\r
+    this._group.find({ $limit: -1 }).subscribe((result) => {\r
+      if (result)\r
+        this.eligibleGroups = result;\r
+    });\r
+\r
+    this.user.get(this.currentUser._id).subscribe((result) => {\r
+      if (result)\r
+        this.defaultGroupId = result['defaultGroup'];\r
+        this.defaultGroupEnabled = result['defaultGroupEnabled'];\r
+\r
+        this._group.get(this.defaultGroupId).subscribe((result) => {\r
+        this.defaultGroup = result;\r
+      });\r
+    });\r
+  }\r
+\r
+  changDefaultGroup(group: Group) {\r
+    this.defaultGroup = group;\r
+  }\r
+\r
+  enableDefaultGroup() {\r
+    this.defaultGroupEnabled = true;\r
+  }\r
+\r
+  disableDefaultGroup() {\r
+    this.defaultGroupEnabled = false;\r
+    \r
+  }\r
+\r
+  update() {\r
+\r
+    this.currentUser.defaultGroupEnabled = this.defaultGroupEnabled;\r
+    this.currentUser.defaultGroup = this.defaultGroup;\r
+    this.cookie.set('currentUser', JSON.stringify(this.currentUser));\r
+    \r
+\r
+    \r
+    let userPatch = {\r
+      _id: this.currentUser._id,\r
+      defaultGroup: this.defaultGroup._id,\r
+      defaultGroupEnabled: this.defaultGroupEnabled\r
+    };\r
+\r
+    this.user.patch(userPatch).subscribe((res) => {\r
+      let snackMessage = 'Successfully Updated Settings';\r
+                this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                    duration: 1500,\r
+                    data: {\r
+                        message: snackMessage\r
+                    }\r
+                })\r
+    })\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/settings/settings.module.spec.ts b/otf-frontend/client/src/app/layout/settings/settings.module.spec.ts
new file mode 100644 (file)
index 0000000..0d694f4
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { SettingsModule } from './settings.module';\r
+\r
+describe('SettingsModule', () => {\r
+  let settingsModule: SettingsModule;\r
+\r
+  beforeEach(() => {\r
+    settingsModule = new SettingsModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(settingsModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/settings/settings.module.ts b/otf-frontend/client/src/app/layout/settings/settings.module.ts
new file mode 100644 (file)
index 0000000..605aac4
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { MatSelectModule } from '@angular/material/select';\r
+import { MatFormFieldModule } from '@angular/material/form-field';\r
+import { FormsModule, ReactiveFormsModule} from '@angular/forms';\r
+import { MatCheckboxModule, MatBadgeModule, MatButtonModule, MatCardModule, MatIconModule, MatInputModule, MatRadioModule, MatSnackBarModule} from '@angular/material';\r
+\r
+import { SettingsRoutingModule } from './settings-routing.module';\r
+import { SettingsComponent } from './settings.component';\r
+import { AlertSnackbarModule } from 'app/shared/modules/alert-snackbar/alert-snackbar.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    SettingsRoutingModule,\r
+    MatSelectModule,\r
+    MatButtonModule,\r
+    FormsModule,\r
+    ReactiveFormsModule,\r
+    MatBadgeModule,\r
+    MatCardModule,\r
+    MatIconModule,\r
+    MatInputModule,\r
+    MatFormFieldModule,\r
+    MatRadioModule,\r
+    MatSnackBarModule,\r
+    AlertSnackbarModule,\r
+    MatCheckboxModule\r
+  ],\r
+  declarations: [SettingsComponent]\r
+})\r
+export class SettingsModule { }\r
diff --git a/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.pug b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.pug
new file mode 100644 (file)
index 0000000..6b0cc0c
--- /dev/null
@@ -0,0 +1,63 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.row.mt-2\r
+  .col-sm-4(*ngIf="data")\r
+    h3 {{ data.testName }}\r
+    p {{ data.testDescription }}\r
+    p.text-muted Updated At {{ data.updatedAt }}\r
+    p.text-muted Created At {{ data.createdAt }}\r
+  .col-sm-8\r
+    mat-card.mb-4\r
+      div(mat-card-image, style="padding: 5% 2px; margin:0px; width:100%; position: relative; cursor: pointer", #canvas, (click)="enlargeBpmn()", [attr.id]="'canvas' + testDefinitionId")\r
+        button(mat-icon-button, color="primary", style="position: absolute; top: 0px; right: 0px; z-index: 100")\r
+          mat-icon zoom_in\r
+\r
+.row(*ngIf="testInstanceList")\r
+  .col-12\r
+    table.mat-elevation-z4(mat-table, [dataSource]="testInstanceList", style="width: 100%")\r
+\r
+      ng-container(matColumnDef="name")\r
+        th(mat-header-cell, *matHeaderCellDef) Instances\r
+        td(mat-cell, *matCellDef="let element")\r
+          a([routerLink]="['/test-instances', {filter: element._id}]") {{ element.testInstanceName}}\r
+\r
+      ng-container(matColumnDef="{{status}}", *ngFor="let status of statusList")\r
+        th(mat-header-cell, *matHeaderCellDef) # {{status.toLowerCase()}}\r
+        td(mat-cell, *matCellDef="let element") \r
+          .dropdown(ngbDropdown, placement="top-right", *ngIf="element[status]")\r
+            a(ngbDropdownToggle) {{ element[status]}}\r
+            .dropdown-menu(ngbDropdownMenu, style="max-height: 200px; overflow-y: scroll")\r
+              div(*ngFor="let execution of testExecutionList | filterBy: {testResult: status}")\r
+                a.dropdown-item([routerLink]="['/control-panel']", [queryParams]="{id: execution._id}", *ngIf="execution.historicTestInstance._id == element._id" )\r
+                  i.fa.fa-fw.fa-bar-chart(style="color: orange")\r
+                  span.pl-1 {{execution.startTime}}\r
+      \r
+      ng-container(matColumnDef="options", stickyEnd)\r
+        th.optionsColumn(mat-header-cell, *matHeaderCellDef)\r
+        td.optionsColumn(mat-cell, *matCellDef="let element") \r
+          .dropdown.options(ngbDropdown, placement="left", style="margin-right: -20px")\r
+            button(mat-icon-button, ngbDropdownToggle) \r
+              mat-icon more_vert\r
+            .dropdown-menu(ngbDropdownMenu)\r
+              a.dropdown-item((click)='executeTestInstance(element)')\r
+                i.fa.fa-fw.fa-refresh(style="color: green")\r
+                span.pl-1 Execute\r
+\r
+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")\r
+\r
+    mat-paginator([pageSizeOptions]="[5, 10]")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.scss b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.scss
new file mode 100644 (file)
index 0000000..3210c46
--- /dev/null
@@ -0,0 +1,27 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+mat-paginator {\r
+    background-color: transparent;\r
+}\r
+\r
+.options .dropdown-toggle::after {\r
+    display:none;\r
+}\r
+\r
+.optionsColumn {\r
+    text-align: right;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.spec.ts b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.spec.ts
new file mode 100644 (file)
index 0000000..eb5296c
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestDefinitionExpandedDetailsComponent } from './test-definition-expanded-details.component';\r
+\r
+describe('TestDefinitionExpandedDetailsComponent', () => {\r
+  let component: TestDefinitionExpandedDetailsComponent;\r
+  let fixture: ComponentFixture<TestDefinitionExpandedDetailsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestDefinitionExpandedDetailsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestDefinitionExpandedDetailsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.ts b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.ts
new file mode 100644 (file)
index 0000000..4e2891d
--- /dev/null
@@ -0,0 +1,216 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, HostListener, AfterContentInit, AfterViewInit, ElementRef, OnDestroy } from '@angular/core';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { TestInstanceService } from 'app/shared/services/test-instance.service';\r
+import { MatTableDataSource, MatPaginator, MatDialog, MatSnackBar } from '@angular/material';\r
+import Modeler from 'bpmn-js';\r
+import { timeInterval } from 'rxjs/operators';\r
+import { Observable } from 'rxjs';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { SchedulingService } from 'app/shared/services/scheduling.service';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { Buffer } from 'buffer';\r
+import { ViewWorkflowModalComponent } from 'app/shared/modules/view-workflow-modal/view-workflow-modal.component';\r
+import { ExecuteService } from 'app/shared/services/execute.service';\r
+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';\r
+\r
+@Component({\r
+  selector: 'app-test-definition-expanded-details',\r
+  templateUrl: './test-definition-expanded-details.component.pug',\r
+  styleUrls: ['./test-definition-expanded-details.component.scss']\r
+})\r
+export class TestDefinitionExpandedDetailsComponent implements OnInit, OnDestroy {\r
+\r
+  @Input() public testDefinitionId;\r
+\r
+  @Input() public events: Observable<void>;\r
+  \r
+  @ViewChild(MatPaginator) instancePaginator: MatPaginator;\r
+  @ViewChild('canvas') canvas: ElementRef;\r
+\r
+  public data = null;\r
+  public dataLength = 0;\r
+  public displayedColumns;\r
+  public foundStatuses = ['name'];\r
+  public statusList = ['COMPLETED', 'SUCCESS', 'UNKNOWN', 'FAILURE', 'STOPPED', 'UNAUTHORIZED', 'FAILED'];\r
+  public testInstanceList = null;\r
+  public testExecutionList = [];\r
+  public viewer;\r
+  public eventSub;\r
+  public bpmnXml;\r
+\r
+  constructor(\r
+    private bpmnFactory: BpmnFactoryService, \r
+    private fileTransfer: FileTransferService, \r
+    private dialog: MatDialog, \r
+    private testDefinition: TestDefinitionService, \r
+    private testInstance: TestInstanceService, \r
+    private testExecution: TestExecutionService, \r
+    private execute: ExecuteService, \r
+    private modal: MatDialog, \r
+    private snack: MatSnackBar\r
+  ) { }\r
+\r
+  async ngOnInit() {\r
+    \r
+    await this.testDefinition.get(this.testDefinitionId).subscribe(\r
+      result => {\r
+        result['createdAt'] = new Date(result['createdAt']).toLocaleString();\r
+        result['updatedAt'] = new Date(result['updatedAt']).toLocaleString();\r
+        this.data = result;\r
+        if(this.data.bpmnInstances){\r
+          this.bpmnFactory.setup({\r
+            mode: 'viewer',\r
+            options: {\r
+              container: this.canvas.nativeElement\r
+            },\r
+            fileId: this.data.bpmnInstances[0].bpmnFileId\r
+          }).then(res => {\r
+            this.viewer = res;\r
+          });\r
+          // this.loadDiagram();\r
+        }\r
+      }\r
+    );\r
+\r
+    this.testInstanceList = new MatTableDataSource();\r
+    this.testInstance.find({\r
+      $limit: -1,\r
+      $sort: {\r
+        createdAt: -1\r
+      },\r
+      testDefinitionId: this.testDefinitionId\r
+    }).subscribe(\r
+      result => {\r
+        this.testInstanceList.data = result;\r
+        this.testInstanceList.paginator = this.instancePaginator;\r
+\r
+        this.testInstanceList.data.forEach(elem => {\r
+          this.setExecutions(elem._id);\r
+        });\r
+\r
+        this.displayedColumns = ['name', 'COMPLETED', 'SUCCESS', 'UNKNOWN', 'FAILURE', 'STOPPED', 'UNAUTHORIZED', 'FAILED', 'options'];\r
+\r
+      }\r
+    )\r
+\r
+    //If parent emeits, diagram will reload\r
+    if(this.events != undefined && this.events){\r
+      this.events.subscribe(() => {\r
+        setTimeout(() => {\r
+          this.loadDiagram();\r
+        }, 500)\r
+      });\r
+    }\r
+  }\r
+\r
+  enlargeBpmn(){\r
+    this.dialog.open(ViewWorkflowModalComponent, {\r
+      data: {\r
+        xml: this.viewer.getBpmnXml()\r
+      },\r
+      width: '100%',\r
+      height: '100%'\r
+    })\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    delete this.events;\r
+  }\r
+\r
+  async setExecutions(instanceId) {\r
+    // ['$limit=-1', '$sort[startTime]=-1', 'testInstanceId=' + instanceId]\r
+    this.testExecution.find({\r
+      $limit: -1,\r
+      $sort: {\r
+        startTime: -1\r
+      },\r
+      'historicTestInstance._id': instanceId\r
+    }).subscribe(\r
+      result => {\r
+        for(let i = 0; i < result['length']; i++){\r
+          result[i].startTime = new Date(result[i].startTime).toLocaleString();\r
+          this.testExecutionList.push(result[i]);\r
+          for(let j = 0; j < this.testInstanceList.data.length; j++){\r
+            if(this.testInstanceList.data[j]._id == instanceId){\r
+              if(!this.testInstanceList.data[j][result[i]['testResult']]){\r
+                this.testInstanceList.data[j][result[i]['testResult']] = 1;\r
+              }else{\r
+                this.testInstanceList.data[j][result[i]['testResult']] += 1;\r
+              }\r
+            }\r
+          }\r
+        }\r
+\r
+        //this.setDisplayColumns();\r
+        // for(let i = 0; i < result[i]; i++){\r
+        //   this.testInstanceList[instanceId] = result;\r
+        // }\r
+        \r
+      }\r
+    );\r
+  }\r
+\r
+  loadDiagram(){\r
+    if(this.viewer && this.data && this.data.bpmnInstances){\r
+      this.viewer.renderDiagram();\r
+    }\r
+  }\r
+\r
+  executeTestInstance(element){\r
+    (element);\r
+    if(element.testDefinitionId){\r
+      const executer = this.modal.open(AlertModalComponent, {\r
+        width: '250px',\r
+        data: {\r
+          type: 'confirmation',\r
+          message: 'Are you sure you want to run ' + element.testInstanceName + '?'\r
+        }\r
+      });\r
+\r
+      executer.afterClosed().subscribe(result => {\r
+        if(result){\r
+          this.execute.create({\r
+            _id : element._id,\r
+            async: true\r
+          }).subscribe((result) => {\r
+            this.snack.openFromComponent(AlertSnackbarComponent, {\r
+              duration: 1500,\r
+              data: {\r
+                message: 'Test Instance Executed'\r
+              }\r
+            });\r
+          },\r
+          (error) => {\r
+            this.modal.open(AlertModalComponent, {\r
+              width: '450px',\r
+              data: {\r
+                type: 'Alert',\r
+                message: 'Failed to execute Test Instance!\n' + JSON.stringify(error)\r
+              }\r
+            });\r
+          })\r
+        }\r
+      });\r
+    }\r
+    \r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog-routing.module.ts b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog-routing.module.ts
new file mode 100644 (file)
index 0000000..b19d3f0
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {RouterModule, Routes} from '@angular/router';\r
+import {TestExecutionsCatalogComponent} from './test-executions-catalog.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '',\r
+        component: TestExecutionsCatalogComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class TestExecutionsCatalogRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.pug b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.pug
new file mode 100644 (file)
index 0000000..21663f3
--- /dev/null
@@ -0,0 +1,61 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  app-page-header([heading]="'Test Executions'", [icon]="'fa-edit'")\r
+\r
+  .card-mb-12\r
+    .pull-left\r
+      mat-form-field\r
+        input(matInput, name="filter", (keyup)="applyFilter($event.target.value)", placeholder="Filter")\r
+    //.pull-right\r
+      button(mat-raised-button, color="primary", (click)="createTestInstance()") New\r
+\r
+    div(style="width: 100%", [hidden]="!loading")\r
+      mat-spinner(style="margin: auto", color="primary")\r
+\r
+    table.mat-elevation-z8(mat-table, [dataSource]="dataSource", style="width: 100%", [hidden]="loading")\r
+\r
+      ng-container(matColumnDef="testInstanceName")\r
+        th(mat-header-cell, *matHeaderCellDef) Test Instance\r
+        td(mat-cell, *matCellDef="let element") {{ (element.historicTestInstance) ? element.historicTestInstance.testInstanceName : 'Does Not Exist' }}\r
+\r
+      ng-container(matColumnDef="testInstanceDescription")\r
+        th(mat-header-cell, *matHeaderCellDef) Description\r
+        td(mat-cell, *matCellDef="let element") {{ (element.testInstanceId) ? element.testInstanceId.testInstanceDescription : ''}}\r
+\r
+      ng-container(matColumnDef="result")\r
+        th(mat-header-cell, *matHeaderCellDef) Result\r
+        td(mat-cell, *matCellDef="let element") {{ element.testResult}}\r
+\r
+      ng-container(matColumnDef="totalTime")\r
+        th(mat-header-cell, *matHeaderCellDef) Total Time\r
+        td(mat-cell, *matCellDef="let element") {{ element.totalTime + ' secs' }} \r
+\r
+      ng-container(matColumnDef="options")\r
+        th(mat-header-cell, *matHeaderCellDef) Options\r
+        td(mat-cell, *matCellDef="let element")\r
+          button.mr-3(mat-mini-fab, matTooltip="Execution Logs", color="primary", [routerLink]="['/control-panel']", [queryParams]="{id: element._id}")\r
+            i.fa.fa-bar-chart\r
+          button.text-white(mat-mini-fab, matTooltip="Delete", color='warn', (click)='deleteTestInstance(element)')\r
+            i.fa.fa-remove\r
+          \r
+\r
+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")\r
+\r
+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")\r
+\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.scss b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.scss
new file mode 100644 (file)
index 0000000..56e842b
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.mat-mini-fab{\r
+    width: 30px !important;\r
+    height: 30px !important;\r
+    line-height: 10px !important;\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.spec.ts b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.spec.ts
new file mode 100644 (file)
index 0000000..6382ba9
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestExecutionsCatalogComponent } from './test-executions-catalog.component';\r
+\r
+describe('TestExecutionsCatalogComponent', () => {\r
+  let component: TestExecutionsCatalogComponent;\r
+  let fixture: ComponentFixture<TestExecutionsCatalogComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestExecutionsCatalogComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestExecutionsCatalogComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.ts b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.ts
new file mode 100644 (file)
index 0000000..a054e59
--- /dev/null
@@ -0,0 +1,162 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ViewContainerRef, OnDestroy } from '@angular/core';\r
+import { MatPaginator, MatDialog, MatTableDataSource, MatSnackBar } from '@angular/material';\r
+import { Router } from '@angular/router';\r
+import { ActivatedRoute } from '@angular/router';\r
+import { ListService } from 'app/shared/services/list.service';\r
+import { TestInstanceService } from 'app/shared/services/test-instance.service';\r
+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+import { routerTransition } from 'app/router.animations';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { Subscription } from 'rxjs';\r
+\r
+@Component({\r
+  selector: 'app-test-executions-catalog',\r
+  templateUrl: './test-executions-catalog.component.pug',\r
+  styleUrls: ['./test-executions-catalog.component.scss'],\r
+  animations: [routerTransition()]\r
+})\r
+export class TestExecutionsCatalogComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+  public dataSource;\r
+  public displayedColumns: string[] = ['testInstanceName', 'testInstanceDescription', 'result', 'totalTime', 'options'];\r
+  public resultsLength;\r
+  public loading = false;\r
+\r
+  @ViewChild(MatPaginator) paginator: MatPaginator;\r
+\r
+  constructor(\r
+    private list: ListService,\r
+    private testExecution: TestExecutionService,\r
+    private modal: MatDialog,\r
+    private route: ActivatedRoute,\r
+    private _groups: GroupService,\r
+    private snack: MatSnackBar\r
+  ) {\r
+  }\r
+\r
+  ngOnInit() {\r
+    this.setComponentData(this._groups.getGroup());\r
+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {\r
+      this.setComponentData(group);\r
+    }));\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.toDestroy.forEach(e => e.unsubscribe());\r
+  }\r
+\r
+  setComponentData(group) {\r
+    if (!group) {\r
+      return;\r
+    }\r
+    this.loading = true;\r
+\r
+    this.dataSource = new MatTableDataSource();\r
+    this.dataSource.paginator = this.paginator;\r
+\r
+    //RG: Hard limit returns object, -1 returns array\r
+    const params = { $limit: 50, groupId: group._id, $populate: ['testInstanceId'], $sort: { startTime: -1 } }//['$limit=-1', '$populate[]=testInstanceId', '$sort[startTime]=-1'];\r
+    if (this.route.snapshot.params['filter']) {\r
+      params['testResult'] = this.route.snapshot.params['filter'].toUpperCase();\r
+    }\r
+    this.testExecution.find(params).subscribe((response) => {\r
+\r
+      let list = response;\r
+      //RG: check if hard limit if so it will be object w/ prop data\r
+      if(!Array.isArray(response) && response.hasOwnProperty('data')){\r
+        list = response['data'];\r
+      }\r
+      for (let i = 0; i < list['length']; i++) {\r
+        const tsDate = new Date(list[i]['startTime']);\r
+        const teDate = new Date(list[i]['endTime']);\r
+        list[i]['totalTime'] = (teDate.getTime() - tsDate.getTime()) / 1000;\r
+      }\r
+      this.dataSource.data = list;\r
+      this.resultsLength = this.dataSource.data.length;\r
+      this.loading = false;\r
+    });\r
+\r
+  }\r
+\r
+  applyFilter(filterValue: string) {\r
+    this.dataSource.filter = filterValue.trim().toLowerCase();\r
+  }\r
+\r
+  createTestInstance() {\r
+    // const create = this.modal.open(TestDefinition, {\r
+    //   width: '450px',\r
+    //   data: {\r
+    //     goal: 'create'\r
+    //   }\r
+    // });\r
+\r
+    // create.afterClosed().subscribe(result => {\r
+    //   this.list.listMap['vth'].currentList.subscribe(x => {\r
+    //     this.dataSource = x;\r
+    //   });\r
+    // });\r
+  }\r
+\r
+\r
+  editTestInstance(th) {\r
+    // const edit = this.modal.open(TestHeadModalComponent, {\r
+    //   width: '450px',\r
+    //   data: {\r
+    //     goal: 'edit',\r
+    //     testHead: th\r
+    //   }\r
+    // });\r
+\r
+    // edit.afterClosed().subscribe(result => {\r
+    //   console.log(result);\r
+    // });\r
+  }\r
+\r
+  deleteTestInstance(te) {\r
+    const deleter = this.modal.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to delete ' + te.testExecutionName + ' ?'\r
+      }\r
+    });\r
+\r
+    deleter.afterClosed().subscribe(result => {\r
+      if (result) {\r
+        this.testExecution.delete(te._id).subscribe(response => {\r
+          this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            duration: 1500,\r
+            data: {\r
+              message: 'Test Execution Deleted'\r
+            }\r
+          });\r
+          this.list.removeElement('te', '_id', te._id + '');\r
+          this.list.listMap['te'].currentList.subscribe(x => {\r
+            this.dataSource.data = x;\r
+            this.resultsLength = x.length;\r
+          });\r
+        });\r
+      }\r
+    });\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.spec.ts b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.spec.ts
new file mode 100644 (file)
index 0000000..e977dad
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {TestExecutionsCatalogModule} from './test-executions-catalog.module';\r
+\r
+describe('TestExecutionsCatalogModule', () => {\r
+    let testExecutionsCatalogModule: TestExecutionsCatalogModule;\r
+\r
+    beforeEach(() => {\r
+        testExecutionsCatalogModule = new TestExecutionsCatalogModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(testExecutionsCatalogModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.ts b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.ts
new file mode 100644 (file)
index 0000000..cc2d6cd
--- /dev/null
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {CommonModule} from '@angular/common';\r
+\r
+import {TestExecutionsCatalogRoutingModule} from './test-executions-catalog-routing.module';\r
+import {TestExecutionsCatalogComponent} from './test-executions-catalog.component';\r
+import {PageHeaderModule} from 'app/shared';\r
+import {FormsModule} from '@angular/forms';\r
+import {FilterPipeModule} from 'ngx-filter-pipe';\r
+import {\r
+    MatButtonModule,\r
+    MatFormFieldModule,\r
+    MatIconModule,\r
+    MatInputModule,\r
+    MatPaginatorModule,\r
+    MatTableModule,\r
+    MatTooltipModule,\r
+    MatSnackBarModule,\r
+    MatProgressSpinnerModule\r
+} from '@angular/material';\r
+import {TestHeadModalModule} from 'app/shared/modules/test-head-modal/test-head-modal.module';\r
+import {AlertModalModule} from 'app/shared/modules/alert-modal/alert-modal.module';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        TestExecutionsCatalogRoutingModule,\r
+        PageHeaderModule,\r
+        FormsModule,\r
+        FilterPipeModule,\r
+        MatButtonModule,\r
+        MatTableModule,\r
+        MatFormFieldModule,\r
+        MatInputModule,\r
+        MatPaginatorModule,\r
+        TestHeadModalModule,\r
+        AlertModalModule,\r
+        MatIconModule,\r
+        MatTooltipModule,\r
+        MatSnackBarModule,\r
+        MatProgressSpinnerModule\r
+    ],\r
+    declarations: [TestExecutionsCatalogComponent]\r
+})\r
+export class TestExecutionsCatalogModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.pug b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.pug
new file mode 100644 (file)
index 0000000..c9c9575
--- /dev/null
@@ -0,0 +1,28 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.col-12(style="background-color: #f5f5f5; padding: 10px") \r
+  mat-spinner(*ngIf="isLoading", [diameter]="25", style="margin: auto")\r
+  div(*ngIf="executionList.length > 0")\r
+    .list-group(*ngFor="let execution of executionList")\r
+      a.list-group-item.list-group-item-action(style="", [routerLink]="['/control-panel']", [queryParams]="{id: execution._id}") \r
+        .pull-left\r
+          i.fa.fa-fw.fa-bar-chart(style="color: orange") \r
+          |  {{execution.startTime}}\r
+        .pull-right\r
+          div([attr.class]="execution.testResult + '-dash'") {{execution.testResult}}\r
+  div(*ngIf="!isLoading && executionList.length == 0", style="text-align:center")\r
+    p There are no executions for this instance.\r
diff --git a/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.scss b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.scss
new file mode 100644 (file)
index 0000000..17106e6
--- /dev/null
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.COMPLETED-dash {\r
+    color: #0d47a1;\r
+}\r
+\r
+.SUCCESS-dash {\r
+    color: #199700;\r
+}\r
+\r
+.FAILURE-dash {\r
+    color: #dd2c00;\r
+}\r
+\r
+.STOPPED-dash {\r
+    color: #ff9100;\r
+}\r
+\r
+.UNAUTHORIZED-dash {\r
+    color: #000000;\r
+}\r
+\r
+.UNKNOWN-dash {\r
+    color: grey;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.spec.ts b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.spec.ts
new file mode 100644 (file)
index 0000000..4828eed
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestInstanceExpandedDetailsComponent } from './test-instance-expanded-details.component';\r
+\r
+describe('TestInstanceExpandedDetailsComponent', () => {\r
+  let component: TestInstanceExpandedDetailsComponent;\r
+  let fixture: ComponentFixture<TestInstanceExpandedDetailsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestInstanceExpandedDetailsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestInstanceExpandedDetailsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.ts b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.ts
new file mode 100644 (file)
index 0000000..baf9c40
--- /dev/null
@@ -0,0 +1,59 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input } from '@angular/core';\r
+import { TestExecutionService } from 'app/shared/services/test-execution.service';\r
+\r
+@Component({\r
+  selector: 'app-test-instance-expanded-details',\r
+  templateUrl: './test-instance-expanded-details.component.pug',\r
+  styleUrls: ['./test-instance-expanded-details.component.scss']\r
+})\r
+export class TestInstanceExpandedDetailsComponent implements OnInit {\r
+\r
+  @Input() public testInstanceId;\r
+  public executionList:any = [];\r
+  public isLoading = true;\r
+\r
+  constructor(private testexecution: TestExecutionService) { }\r
+\r
+  ngOnInit() {\r
+    this.testexecution.find({\r
+      $limit: 100, \r
+      $sort: { \r
+        startTime: -1 \r
+      }, \r
+      $or: [\r
+        { "historicTestInstance._id": this.testInstanceId},\r
+        { testInstanceId: this.testInstanceId }\r
+      ],\r
+      $select: ['startTime', 'testResult']\r
+      \r
+    }).subscribe(\r
+      result => {\r
+        for(let i = 0; i < result['data']['length']; i++){\r
+          result['data'][i]['startTime'] = new Date(result['data'][i]['startTime']).toLocaleString();\r
+        }\r
+        this.executionList = result['data'];\r
+        this.isLoading = false;\r
+      }, \r
+      err => {\r
+        this.isLoading = false;\r
+      }\r
+    );\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog-routing.module.ts b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog-routing.module.ts
new file mode 100644 (file)
index 0000000..69d5e33
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {RouterModule, Routes} from '@angular/router';\r
+import {TestInstancesCatalogComponent} from './test-instances-catalog.component';\r
+\r
+const routes: Routes = [{\r
+    path: '', component: TestInstancesCatalogComponent\r
+}];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class TestInstancesCatalogRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.pug b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.pug
new file mode 100644 (file)
index 0000000..446c892
--- /dev/null
@@ -0,0 +1,72 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition]).mb-3\r
+  \r
+  .row\r
+    .col\r
+      app-page-header.pull-left([heading]="'Test Instances'", [icon]="'fa-edit'")\r
+      button.mr-2.pull-right(mat-raised-button, color="primary", (click)="createTestInstance()") New\r
+\r
+\r
+\r
+  .row\r
+    .col.mt-2\r
+      //- Delete\r
+      button.mr-2.pull-right(color="primary", matTooltip="Delete Test Instance", mat-icon-button, (click)="deleteMultipleTestInstance()", [disabled]="(!hasSelectedRows)") \r
+        mat-icon delete_forever\r
+      //- Clone\r
+      button.mr-2.pull-right(color="primary", matTooltip="Clone Test Instance", mat-icon-button, (click)="cloneTestInstance()", [disabled]="(!selectedSingleRow)") \r
+        mat-icon insert_drive_file\r
+      //- Edit\r
+      button.mr-2.pull-right(color="primary", matTooltip="Edit Test Instance", mat-icon-button, (click)="editTestInstance()", [disabled]="(!selectedSingleRow)")\r
+        mat-icon edit\r
+      //- Execute\r
+      button.mr-2.pull-right(color="primary", matTooltip="Execute Test Instance", mat-icon-button, (click)="executeMultipleTestInstance()", *ngIf="(selectedUnlockedRows)")\r
+        mat-icon play_circle_outline\r
+      //- Schedule\r
+      button.mr-2.pull-right(color="primary", matTooltip="Schedule Test Instance", mat-icon-button, (click)="schedule()", *ngIf="(selectedUnlockedRows && selectedSingleRow)")\r
+        mat-icon date_range\r
+\r
+\r
+  .row\r
+    .col-md\r
+      ag-grid-angular.ag-theme-material(\r
+        style="width:100%; height: 600px",\r
+        [rowData]="rowData",\r
+        [columnDefs]="columnDefs",\r
+        rowSelection="multiple",\r
+        [rowMultiSelectWithClick]="true",\r
+        (rowSelected)="onRowSelected($event)",\r
+        (gridReady)="onGridReady($event)", \r
+        [singleClickEdit]="true",\r
+        [gridOptions]="gridOptions",\r
+        (rowDataChanged)="selectActiveInstance($event)"\r
+        )\r
+\r
+    .col-md-3(*ngIf="selectedSingleRow")\r
+      h1 Executions\r
+      div(*ngFor = "let ti of rowData")\r
+        app-test-instance-expanded-details(*ngIf="ti._id == selectedRows[0]._id", [testInstanceId]="selectedRows[0]._id")\r
+\r
+\r
+        \r
+\r
+   \r
+\r
+\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.scss b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.scss
new file mode 100644 (file)
index 0000000..110ce5b
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.mat-mini-fab{\r
+    width: 30px !important;\r
+    height: 30px !important;\r
+    line-height: 10px !important;\r
+}\r
+.dropdown-menu{\r
+  z-index: 10;\r
+}\r
+.dropdown-toggle::after {\r
+    display:none;\r
+}\r
+\r
+tr.example-detail-row {\r
+    height: 0;\r
+  }\r
+  \r
+  tr.example-element-row:not(.example-expanded-row):hover {\r
+    background: #f5f5f5;\r
+    cursor: pointer;\r
+  }\r
+  \r
+  tr.example-element-row:not(.example-expanded-row):active {\r
+    background: #efefef;\r
+    cursor: pointer;\r
+  }\r
+\r
+  .example-element-row td {\r
+    border-bottom-width: 0;\r
+  }\r
+\r
+  table {\r
+    width: 100%;\r
+  }\r
+\r
+  .options {\r
+    flex: 0 0 10px !important;\r
+  }
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.spec.ts b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.spec.ts
new file mode 100644 (file)
index 0000000..11ccb4e
--- /dev/null
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';\r
+\r
+import {TestInstancesCatalogComponent} from './test-instances-catalog.component';\r
+\r
+describe('TestInstancesCatalogComponent', () => {\r
+    let component: TestInstancesCatalogComponent;\r
+    let fixture: ComponentFixture<TestInstancesCatalogComponent>;\r
+\r
+    beforeEach(async(() => {\r
+        TestBed.configureTestingModule({\r
+            declarations: [TestInstancesCatalogComponent]\r
+        }).compileComponents()\r
+            .then((arg) => {\r
+                // handle\r
+            })\r
+            .catch((err) => {\r
+                // handle error\r
+            });\r
+    }));\r
+\r
+    beforeEach(() => {\r
+        fixture = TestBed.createComponent(TestInstancesCatalogComponent);\r
+        component = fixture.componentInstance;\r
+        fixture.detectChanges();\r
+    });\r
+\r
+    it('should create', () => {\r
+        expect(component).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.ts b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.ts
new file mode 100644 (file)
index 0000000..cb5a6e5
--- /dev/null
@@ -0,0 +1,436 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ViewContainerRef, AfterViewInit, OnDestroy, ViewChildren, ElementRef, QueryList } from '@angular/core';\r
+import { MatDialog, MatPaginator, MatSnackBar, MatTableDataSource, MatListItem } from '@angular/material';\r
+import { ActivatedRoute, Router } from '@angular/router';\r
+import { ListService } from '../../shared/services/list.service';\r
+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';\r
+import { TestInstanceService } from '../../shared/services/test-instance.service';\r
+import { routerTransition } from 'app/router.animations';\r
+import { SchedulingService } from '../../shared/services/scheduling.service';\r
+import { TestInstanceModalComponent } from '../../shared/modules/test-instance-modal/test-instance-modal.component';\r
+import { AlertSnackbarComponent } from '../../shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { ScheduleTestModalComponent } from '../../shared/modules/schedule-test-modal/schedule-test-modal.component';\r
+import { animate, state, style, transition, trigger } from '@angular/animations';\r
+import { TestDefinitionService } from '../../shared/services/test-definition.service';\r
+import { Observable, Subscription } from 'rxjs';\r
+import { ExecuteService } from 'app/shared/services/execute.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { GridOptions } from "ag-grid-community";\r
+\r
+\r
+@Component({\r
+    selector: 'app-test-instances-catalog',\r
+    templateUrl: './test-instances-catalog.component.pug',\r
+    styleUrls: ['./test-instances-catalog.component.scss'],\r
+    animations: [routerTransition(),\r
+    trigger('detailExpand', [\r
+        state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),\r
+        state('expanded', style({ height: '*' })),\r
+        transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),\r
+    ])\r
+    ]\r
+})\r
+export class TestInstancesCatalogComponent implements OnInit, AfterViewInit, OnDestroy {\r
+\r
+\r
+    public resultsLength;\r
+    public loading = false;\r
+\r
+\r
+    public columnDefs = [\r
+        {headerName: '', field: 'disabled', cellRenderer: this.disabledIndicator, width: 100, hide: false, checkboxSelection:true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true},\r
+        {headerName: 'Name', field: 'testInstanceName', sortable: true, filter: true, resizable: true, width: 300},\r
+        {headerName: 'Description', field: 'testInstanceDescription', sortable: true, filter: true, resizable: true, width: 100},\r
+        {headerName: 'Id', field: '_id', sortable: true, filter: true, resizable: true, width: 200, editable: true},\r
+        {headerName: 'Test Definition', field: 'testDefinitionId.testName', sortable: true, filter: true, resizable: true}\r
+        \r
+      ];\r
+\r
+    public selectedRows;\r
+    public hasSelectedRows;\r
+    public selectedSingleRow;\r
+    private gridApi;\r
+    private gridColumnApi;\r
+    public selectedUnlockedRows;\r
+    public rowData;\r
+\r
+    public gridOptions: GridOptions;\r
+\r
+    \r
+    public params;\r
+\r
+    private subscriptions: Subscription[] = [];\r
+\r
+    @ViewChild(MatPaginator) paginator: MatPaginator;\r
+\r
+    constructor(\r
+\r
+        private router: Router,\r
+        private viewRef: ViewContainerRef,\r
+        private testInstance: TestInstanceService,\r
+        private modal: MatDialog,\r
+        private schedulingService: SchedulingService,\r
+        private snack: MatSnackBar,\r
+        private route: ActivatedRoute,\r
+        private testDefinitionService: TestDefinitionService,\r
+        private _execute: ExecuteService,\r
+        private _groups: GroupService\r
+    ) {  }\r
+\r
+    ngOnInit() {\r
+        \r
+        this.setComponentData(this._groups.getGroup());\r
+\r
+        this.subscriptions.push(this._groups.groupChange().subscribe(group => {\r
+            this.setComponentData(group);\r
+            \r
+        }));\r
+\r
+        // this.subscriptions.push(this._groups.groupChange().subscribe( group => {\r
+        //     if(!group["_id"]){\r
+        //         this.setComponentData(this._groups.getGroup());\r
+        //     }\r
+        //     this.setComponentData(group);\r
+        // }));\r
+\r
+\r
+        this.route.queryParams.subscribe( params => {\r
+           \r
+            this.params = params;\r
+\r
+\r
+        });\r
+\r
+\r
+    }\r
+\r
+    setComponentData(group) {\r
+\r
+        if(!group){\r
+            return;\r
+        }\r
+        this.loading = true;\r
+        let params = {\r
+            groupId: group['_id'],\r
+            $limit: -1,\r
+            $populate: ['testDefinitionId'],\r
+            $sort: {\r
+                createdAt: -1\r
+            },\r
+            $select: ['testInstanceName', 'testInstanceDescription', 'testDefinitionId.testName', 'disabled']\r
+        }\r
+\r
+        if (this.route.snapshot.params['filter']) {\r
+            params['_id'] = this.route.snapshot.params['filter'];\r
+        }\r
+\r
+        this.testInstance.find(params).subscribe((list) => {\r
+            this.resultsLength = list['length'];\r
+            this.loading = false;\r
+            this.rowData = list;\r
+        },\r
+        err => {\r
+            console.log(err);\r
+        });\r
+        \r
+    }\r
+\r
+    ngAfterViewInit() {\r
+   \r
+    }\r
+\r
+    ngOnDestroy() {\r
+        this.subscriptions.forEach(e => e.unsubscribe());\r
+    }\r
+\r
+\r
+    schedule() {\r
+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName, testDefinitionId}) => ({_id, testInstanceName, testDefinitionId}));\r
+        console.log("The new element is: "+JSON.stringify(this.selectedRows[0]._id));\r
+        \r
+        console.log("Here is the selected Row: "+JSON.stringify(this.gridApi.getSelectedRows()[0]));\r
+        const dialogRef = this.modal.open(ScheduleTestModalComponent, {\r
+            width: '90%',\r
+            data: {\r
+                id: this.selectedRows[0]._id\r
+            }\r
+        });\r
+\r
+        dialogRef.afterClosed().subscribe(result => {\r
+            /*if(result != ''){\r
+              this.test_instance_selected = result;\r
+              this.strategy_selected = true;\r
+            }else{\r
+              this.strategy_selected = false;\r
+            }*/\r
+        });\r
+    }\r
+\r
+    executeMultipleTestInstance(){\r
+        for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){\r
+            this.executeTestInstance(i);\r
+        }\r
+    }\r
+\r
+    executeTestInstance(ti) {\r
+        \r
+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName, testDefinitionId}) => ({_id, testInstanceName, testDefinitionId}));\r
+\r
+\r
+        if (this.selectedRows[ti].testDefinitionId) {\r
+            const executer = this.modal.open(AlertModalComponent, {\r
+                width: '250px',\r
+                data: {\r
+                    type: 'confirmation',\r
+                    message: 'Are you sure you want to run ' + this.selectedRows[ti].testInstanceName + '?'\r
+                }\r
+            });\r
+\r
+            executer.afterClosed().subscribe(result => {\r
+                if (result) {\r
+                    this._execute.create({\r
+                        _id: this.selectedRows[ti]._id,\r
+                        async: true\r
+                    }).subscribe((result) => {\r
+                        console.log(result);\r
+                        if (result) {\r
+                            this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                                duration: 1500,\r
+                                data: {\r
+                                    message: 'Test Instance Executed'\r
+                                }\r
+                            });\r
+                        }\r
+                    },\r
+                        (error) => {\r
+                            console.log(error);\r
+                            this.modal.open(AlertModalComponent, {\r
+                                width: '450px',\r
+                                data: {\r
+                                    type: 'Alert',\r
+                                    message: 'Failed to execute Test Instance!\n' + JSON.stringify(error)\r
+                                }\r
+                            });\r
+                        })\r
+                }\r
+            });\r
+        }\r
+\r
+    }\r
+\r
+    createTestInstance() {\r
+        const create = this.modal.open(TestInstanceModalComponent, {\r
+            width: '90%',\r
+            data: null,\r
+            disableClose: true\r
+        });\r
+\r
+        create.afterClosed().subscribe(result => {\r
+          this.ngOnInit();\r
+        });\r
+    }\r
+\r
+\r
+    editTestInstance() {\r
+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName}) => ({_id, testInstanceName}));\r
+\r
+        const edit = this.modal.open(TestInstanceModalComponent, {\r
+            data: {\r
+                ti: this.selectedRows[0]._id,\r
+                isEdit: true\r
+            },\r
+            width: '90%',\r
+            disableClose: true\r
+        });\r
+\r
+        edit.afterClosed().subscribe(result => {\r
+\r
+        });\r
+    }\r
+\r
+    cloneTestInstance() {\r
+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName}) => ({_id, testInstanceName}));\r
+        this.testInstance.get(this.selectedRows[0]._id).subscribe(\r
+            result => {\r
+                var temp = Object.assign({}, result);\r
+                delete result['_id'];\r
+                delete result['createdAt'];\r
+                delete result['updatedAt'];\r
+                if (this.selectedRows[0].testInstanceName) {\r
+                    result['testInstanceName'] = this.selectedRows[0].testInstanceName + '_Clone';\r
+                } else {\r
+                    result['testInstanceName'] = result['testInstanceName'] + '_Clone';\r
+                }\r
+                this.testInstance.create(result).subscribe(\r
+                    resp => {\r
+                        //this.editTestInstance(resp);\r
+                        this.editTestInstance();\r
+                    },\r
+                    err => {\r
+                        if (err) {\r
+                      \r
+                            result['_id'] = temp['_id'];\r
+                           \r
+                            //this.cloneTestInstance(result);\r
+                        }\r
+                    }\r
+                );\r
+            }\r
+        )\r
+    }\r
+\r
+    deleteMultipleTestInstance(){\r
+        for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){\r
+            this.deleteTestInstance(i);\r
+        }\r
+    }\r
+\r
+    deleteTestInstance(ti) {\r
+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName}) => ({_id, testInstanceName}));\r
+\r
+\r
+        const deleter = this.modal.open(AlertModalComponent, {\r
+            width: '250px',\r
+            data: {\r
+                type: 'confirmation',\r
+                message: 'Are you sure you want to delete ' + this.selectedRows[ti].testInstanceName + ' ? Executions of this instance will no longer display everything.'\r
+            }\r
+        });\r
+\r
+        deleter.afterClosed().subscribe(result => {\r
+            if (result) {\r
+                this.testInstance.delete(this.selectedRows[ti]._id).subscribe(response => {\r
+                    this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                        duration: 1500,\r
+                        data: {\r
+                            message: 'Test Instance Deleted'\r
+                        }\r
+                    });\r
+\r
+                });\r
+                this.setComponentData(this._groups.getGroup());\r
+            }\r
+        });\r
+        \r
+    }\r
+\r
+\r
+    disabledIndicator(params){\r
+        if (params.value){\r
+          return `<mat-icon class="mat-icon mat-icon-no-color" role="img" >\r
+           locked</mat-icon>`;   \r
+    }\r
+\r
\r
+    }\r
+\r
+    setParams(element) {\r
+   \r
+        if (JSON.stringify(element) == JSON.stringify({testInstanceId: this.params.testInstanceId})){\r
+            element = {};\r
+        }\r
+\r
+        \r
+        this.router.navigate([], {\r
+           //queryParams: {testInstanceId: element.testInstanceId, page: this.paginator.pageIndex, instancePerPage: this.paginator.pageSize }\r
+            queryParams: {testInstanceId: element._id}\r
+        })\r
+           \r
+    }\r
+\r
+    onGridReady(params){\r
+        this.gridApi = params.api;\r
+        console.log(params.columnApi.autoSizeColumns)\r
+        this.gridColumnApi = params.columnApi;\r
+    \r
+        //auto size the column widths\r
+        this.gridColumnApi.autoSizeColumns(['name']);\r
+\r
+      }\r
+\r
+      selectActiveInstance($event){\r
+        if(this.params.testInstanceId)\r
+        {\r
+            this.gridApi.forEachNode( (node, index) => {\r
+\r
+                if(node.data._id ==this.params.testInstanceId)\r
+                {\r
+                    // Pre selects the row that was last selected when on the page\r
+                    node.setSelected(true, true);\r
+                    //Vertically scrolls to that row so it is visible\r
+                    this.gridApi.ensureIndexVisible(index, "middle");\r
+                    \r
+                }\r
+              \r
+            });\r
+        }\r
+        \r
+      }\r
+\r
+\r
+    onRowSelected(event){\r
+\r
+        this.selectedRows = this.gridApi.getSelectedRows().map(({ _id, disabled, testInstanceName  }) => ({ _id, disabled, testInstanceName}));\r
+\r
+\r
+        \r
+        if(event.api.getSelectedNodes().length > 0){\r
+          this.hasSelectedRows = true;\r
+          \r
+            //Checks for all Unlocked rows\r
+            for (let i = 0; i < event.api.getSelectedNodes().length; i++ )\r
+            {\r
+                if(!this.selectedRows[i].disabled)\r
+                {\r
+                    this.selectedUnlockedRows = true;\r
+                }\r
+                else{\r
+                    this.selectedUnlockedRows = false;\r
+                    break;\r
+                }\r
+            }\r
+        }\r
+        else{\r
+          this.hasSelectedRows = false;\r
+          this.selectedUnlockedRows = false;\r
+          \r
+          this.setParams({_id: null});\r
+        }\r
+\r
+        \r
+        //One Row was selected\r
+        if((event.api.getSelectedNodes().length == 1)){\r
+          this.selectedSingleRow = true;\r
+         \r
+          this.setParams({_id: this.selectedRows[0]._id});\r
+         \r
+        }else{\r
+          this.selectedSingleRow = false;\r
+          this.setParams({_id: null});\r
+        }\r
+       \r
+    }\r
+\r
+\r
+\r
+      \r
+\r
+\r
+\r
+    \r
+}\r
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.spec.ts b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.spec.ts
new file mode 100644 (file)
index 0000000..5823da5
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {TestInstancesCatalogModule} from './test-instances-catalog.module';\r
+\r
+describe('TestInstancesCatalogModule', () => {\r
+    let testInstancesCatalogModule: TestInstancesCatalogModule;\r
+\r
+    beforeEach(() => {\r
+        testInstancesCatalogModule = new TestInstancesCatalogModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(testInstancesCatalogModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.ts b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.ts
new file mode 100644 (file)
index 0000000..6d85016
--- /dev/null
@@ -0,0 +1,65 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {CommonModule} from '@angular/common';\r
+import {TestInstancesCatalogRoutingModule} from './test-instances-catalog-routing.module';\r
+import {TestInstancesCatalogComponent} from './test-instances-catalog.component';\r
+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';\r
+import {PageHeaderModule} from '../../shared';\r
+import {FormsModule} from '@angular/forms';\r
+import {FilterPipeModule} from 'ngx-filter-pipe';\r
+import {MatButtonModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatTableModule, MatSnackBarModule, MatTooltipModule, MatIconModule} from '@angular/material';\r
+import {TestHeadModalModule} from '../../shared/modules/test-head-modal/test-head-modal.module';\r
+import { AlertSnackbarModule } from 'app/shared/modules/alert-snackbar/alert-snackbar.module';\r
+import {TestInstanceModalModule} from '../../shared/modules/test-instance-modal/test-instance-modal.module';\r
+import { ScheduleTestModalModule } from '../../shared/modules/schedule-test-modal/schedule-test-modal.module';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\r
+import { TestInstanceExpandedDetailsComponent } from 'app/layout/test-instance-expanded-details/test-instance-expanded-details.component';\r
+import { AgGridModule } from 'ag-grid-angular';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        TestInstancesCatalogRoutingModule,\r
+        PageHeaderModule,\r
+        ScheduleTestModalModule,\r
+        FormsModule,\r
+        FilterPipeModule,\r
+        MatButtonModule,\r
+        MatTableModule,\r
+        MatFormFieldModule,\r
+        MatInputModule,\r
+        MatPaginatorModule,\r
+        TestHeadModalModule,\r
+        MatSnackBarModule,\r
+        AlertModalModule,\r
+        MatSnackBarModule,\r
+        AlertSnackbarModule,\r
+        TestInstanceModalModule,\r
+        MatTooltipModule,\r
+        MatIconModule,\r
+        NgbModule,\r
+        MatProgressSpinnerModule,\r
+        AgGridModule.withComponents([])\r
+    ],\r
+    declarations: [TestInstancesCatalogComponent,\r
+        TestInstanceExpandedDetailsComponent],\r
+    entryComponents: [TestInstanceExpandedDetailsComponent]\r
+})\r
+export class TestInstancesCatalogModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.pug b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.pug
new file mode 100644 (file)
index 0000000..87d49a5
--- /dev/null
@@ -0,0 +1,64 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+mat-card.mb-3\r
+    mat-card-header \r
+        mat-card-title \r
+            h4(*ngIf="testDefinition?.testName") {{ testDefinition.testName }} \r
+        mat-card-subtitle(style="margin-bottom: 0px")\r
+            div(*ngIf="testDefinition?.testDescription") {{testDefinition.testDescription }}\r
+    mat-card-content\r
+        .row(*ngIf="testDefinition")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testDefinition?.processDefinitionKey")\r
+                    input(matInput, placeholder="Process Definition Key", type="text", [value]="testDefinition.processDefinitionKey", disabled, name="defKey")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testDefinition?.disabled != undefined") \r
+                    input(matInput, placeholder="Is Disabled", type="text", [value]="testDefinition.disabled", disabled, name="disabled")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testDefinition") \r
+                    input(matInput, placeholder="Number Of Versions", type="text", [value]="numOfVersions", disabled, name="numOfVersions")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testDefinition?.groupId")\r
+                    input(matInput, placeholder="Group Id", type="text", [value]="testDefinition.groupId", disabled, name="group")\r
+            //- .col-sm\r
+            //-     mat-form-field(style="width:50px",*ngIf="testDefinition?.isPublic")\r
+            //-         input(matInput, placeholder="Is Public", type="text", [value]="testDefinition.isPublic", disabled, name="public")\r
+\r
+div(style="position: relative")\r
+  .row\r
+    .col-12\r
+      .pull-left\r
+        mat-form-field(style="width:110px")\r
+          input(matInput, [matDatepicker]="fromPicker", placeholder="From Date", [(ngModel)]="stats.filters.startDate")\r
+          mat-datepicker-toggle(matSuffix, [for]="fromPicker")\r
+          mat-datepicker(#fromPicker)\r
+        mat-form-field.ml-2(style="width:110px")\r
+          input(matInput, [matDatepicker]="toPicker", placeholder="To Date", [(ngModel)]="stats.filters.endDate")\r
+          mat-datepicker-toggle(matSuffix, [for]="toPicker")\r
+          mat-datepicker(#toPicker)\r
+        button.ml-2(mat-icon-button, (click)="getData()") \r
+          mat-icon arrow_forward\r
+          \r
+      .pull-right\r
+        mat-form-field\r
+          input(matInput, [ngModel]="stats.executionList?.length", placeholder="Total Executions", disabled)\r
+\r
+  .row\r
+    .col-12\r
+      mat-card\r
+        mat-card-content\r
+          app-line-chart(height="201px")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.scss b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.spec.ts b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.spec.ts
new file mode 100644 (file)
index 0000000..24aaec8
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestDefinitionDetailsComponent } from './test-definition-details.component';\r
+\r
+describe('TestDefinitionDetailsComponent', () => {\r
+  let component: TestDefinitionDetailsComponent;\r
+  let fixture: ComponentFixture<TestDefinitionDetailsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestDefinitionDetailsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestDefinitionDetailsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.ts b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.ts
new file mode 100644 (file)
index 0000000..660fa62
--- /dev/null
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, OnDestroy } from '@angular/core';\r
+import { ActivatedRoute } from '@angular/router';\r
+import { Subscription } from 'rxjs';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+import { TestDefinition } from 'app/shared/models/test-definition.model';\r
+import { StatsService } from 'app/layout/components/stats/stats.service';\r
+\r
+@Component({\r
+  selector: 'app-test-definition-details',\r
+  templateUrl: './test-definition-details.component.pug',\r
+  styleUrls: ['./test-definition-details.component.scss']\r
+})\r
+export class TestDefinitionDetailsComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  public testDefinition: TestDefinition;\r
+  \r
+  constructor(\r
+    private route: ActivatedRoute, \r
+    private _testDefinition: TestDefinitionService,\r
+    public stats: StatsService\r
+  ) { }\r
+\r
+  ngOnInit() {\r
+    this.toDestroy.push(this.route.params.subscribe(params => {\r
+      \r
+      if(params.id){\r
+        this._testDefinition.get(params.id).subscribe(\r
+          res => {\r
+            \r
+            this.testDefinition = res as TestDefinition;\r
+          },\r
+          err => {\r
+            \r
+          })\r
+\r
+        this.getData(params.id);\r
+      }\r
+    }));\r
+  }\r
+\r
+  get numOfVersions(){\r
+    if(this.testDefinition['bpmnInstances']){\r
+      return this.testDefinition['bpmnInstances'].length;\r
+    }\r
+    return 0;\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.toDestroy.forEach(elem => elem.unsubscribe());\r
+  }\r
+\r
+  getData(testDefinitionId?){\r
+    if(!testDefinitionId){\r
+      testDefinitionId = this.testDefinition._id\r
+    }\r
+\r
+    if(!testDefinitionId){\r
+      return;\r
+    }\r
+\r
+    this.stats.getDefaultData(1, {\r
+      'historicTestDefinition._id': testDefinitionId,\r
+      $select: [\r
+        'startTime',\r
+        'endTime',\r
+        "historicTestDefinition._id",\r
+        "historicTestDefinition.testName",\r
+        "historicTestInstance._id",\r
+        "historicTestInstance.testInstanceName",\r
+        "testHeadResults.startTime",\r
+        "testHeadResults.endTime",\r
+        "testHeadResults.testHeadName",\r
+        "testHeadResults.testHeadId",\r
+        "testHeadResults.testHeadGroupId",\r
+        "testHeadResults.statusCode",\r
+        'testResult'\r
+      ],\r
+      $limit: -1,\r
+      $sort: {\r
+        startTime: 1\r
+      },\r
+      startTime: {\r
+        $gte: this.stats.filters.startDate,\r
+        $lte: this.stats.filters.endDate\r
+      }\r
+    });\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/tests/tests-routing.module.ts b/otf-frontend/client/src/app/layout/tests/tests-routing.module.ts
new file mode 100644 (file)
index 0000000..e434133
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { TestsComponent } from './tests.component';\r
+import { TestDefinitionDetailsComponent } from './test-definition-details/test-definition-details.component';\r
+\r
+const routes: Routes = [\r
+  { path:'', component: TestsComponent },\r
+  { path:':id', component: TestDefinitionDetailsComponent}\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class TestsRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/tests/tests.component.pug b/otf-frontend/client/src/app/layout/tests/tests.component.pug
new file mode 100644 (file)
index 0000000..a8fc774
--- /dev/null
@@ -0,0 +1,180 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+\r
+  .row\r
+    .col\r
+      .pull-left\r
+        app-page-header([heading]="'Test Definitions'", [icon]="'fa-edit'")\r
+      .pull-right\r
+        button(mat-raised-button, color="primary", (click)="create()") New\r
+  //-.card-mb-12\r
+    .pull-left\r
+      mat-form-field\r
+        input(matInput, name="filter", (keyup)="applyFilter($event.target.value)", placeholder="Filter")\r
+    .pull-right\r
+      button(mat-raised-button, color="primary", (click)="create()") New\r
+\r
+    div(style="width: 100%", [hidden]="!loading")\r
+      mat-spinner(style="margin: auto", color="primary")\r
+    //- \r
+     table.mat-elevation-z8(mat-table, *ngIf="dataSource && dataSource.data && dataSource.data.length > 0", [dataSource]="dataSource", style="width: 100%", [hidden]="loading")\r
+    \r
+      ng-container(matColumnDef="lock")\r
+        th(mat-header-cell, *matHeaderCellDef) \r
+        td(mat-cell, *matCellDef="let element", (click)="expand(element)")\r
+          div.mr-4\r
+            i.fa.fa-lock(*ngIf="element.disabled") \r
+\r
+      ng-container(matColumnDef="name")\r
+        th(mat-header-cell, *matHeaderCellDef) Name\r
+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element.testName }}\r
+\r
+      ng-container(matColumnDef="description")\r
+        th(mat-header-cell, *matHeaderCellDef) Description\r
+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element.testDescription }}\r
+\r
+      ng-container(matColumnDef="id")\r
+        th(mat-header-cell, *matHeaderCellDef) Id\r
+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element._id }}\r
+\r
+      ng-container(matColumnDef="processDefinitionKey")\r
+        th(mat-header-cell, *matHeaderCellDef) Process Definition Key\r
+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element.processDefinitionKey }}\r
+\r
+      ng-container(matColumnDef="options")\r
+        th(mat-header-cell, *matHeaderCellDef) Options\r
+        td(mat-cell, *matCellDef="let element")\r
+          .dropdown(ngbDropdown, placement="bottom-right")\r
+            button(mat-mini-fab, color="primary", ngbDropdownToggle)\r
+              i.fa.fa-caret-down\r
+              //mat-icon more_vert\r
+            .dropdown-menu(ngbDropdownMenu)\r
+              a.dropdown-item(*ngIf="isDeployed(element) && !element.disabled", (click)='createInstance(element)')\r
+                i.fa.fa-fw.fa-plus(style="color: #005000")\r
+                span.pl-1 Create Instance\r
+              //- a.dropdown-item((click)='view(element)')\r
+              //-   i.fa.fa-fw.fa-eye(style="color: #ff9100")\r
+              //-   span.pl-1 View\r
+              a.dropdown-item(*ngIf="element.disabled", (click)='unlock(element)')\r
+                i.fa.fa-fw.far.fa-unlock(style="color: black")\r
+                span.pl-1 Unlock\r
+              a.dropdown-item(*ngIf="!element.disabled", (click)='lock(element)')\r
+                i.fa.fa-fw.far.fa-lock(style="color: black")\r
+                span.pl-1 Lock\r
+              a.dropdown-item(*ngIf="favorites.indexOf(element._id) < 0", (click)='favorite(element)')\r
+                i.fa.fa-fw.far.fa-star(style="color: yellow")\r
+                span.pl-1 Favorite\r
+              a.dropdown-item(*ngIf="favorites.indexOf(element._id) >= 0", (click)='unfavorite(element)')\r
+                i.fa.fa-fw.fas.fa-star(style="color: yellow")\r
+                span.pl-1 Unfavorite\r
+              a.dropdown-item((click)='edit(element)')\r
+                i.fa.fa-fw.fa-pencil(style="color: #0d47a1")\r
+                span.pl-1 Edit\r
+              a.dropdown-item([routerLink]="['/modeler']", [queryParams]="{testDefinitionId: element._id}")\r
+                i.fa.fa-fw.bpmn-icon-bpmn-io(style="color: green")\r
+                span.pl-1 Modeler\r
+              a.dropdown-item((click)='delete(element)')\r
+                i.fa.fa-fw.fa-remove(style="color: #dd2c00")\r
+                span.pl-1 Delete\r
+          //- button.mr-3(mat-mini-fab, matTooltip="View Workflow", color="accent", (click)='view(element)')\r
+          //-   i.fa.fa-eye\r
+          //- button.mr-3(mat-mini-fab, matTooltip="Edit", color="primary", (click)='edit(element)')\r
+          //-   i.fa.fa-pencil\r
+          //- button.text-white(mat-mini-fab, matTooltip="Delete", color='warn', (click)='delete(element)')\r
+          //-   i.fa.fa-remove\r
+\r
+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")\r
+\r
+    //-mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")\r
+\r
+    \r
+  \r
+  .row.mt-2\r
+    .col\r
+\r
+      //- Create\r
+      button.mr-2.pull-right(color="primary", matTooltip="Create Test Instance", mat-icon-button, (click)="createInstance()", [disabled] = "((!selectedSingleRow) || (selectedLockedRows))")\r
+        mat-icon add\r
+      //- Lock\r
+      button.mr-4.pull-right(color="primary", matTooltip="Lock Test Definition", mat-icon-button, (click)="lockMultiple()", [disabled]="(!hasSelectedRows)", [hidden]="(!selectedUnlockedRows)")\r
+        mat-icon lock\r
+      //- Unlock\r
+      button.mr-2.pull-right(color="primary", matTooltip="Unlock Test Definition", mat-icon-button, (click)="unlockMultiple()", [disabled]="", [hidden] = "((!selectedLockedRows) || (!selectedRows))")\r
+        mat-icon lock_open\r
+\r
+      //- Edit\r
+      button.mr-2.pull-right(color="primary", matTooltip="Edit Test Definition", mat-icon-button, (click)="edit()", [disabled]="(!selectedSingleRow)") \r
+        mat-icon edit\r
+      //- Delete\r
+      button.mr-2.pull-right(color="primary", matTooltip="Delete Test Definition", mat-icon-button, (click)="deleteMultiple()", [disabled]="!hasSelectedRows") \r
+        mat-icon delete_forever\r
+      //- Modeler\r
+      button.mr-2.pull-right(mat-raised-button, color="primary", (click)="testDefinitionModeler()", [disabled]="(!selectedSingleRow)") Modeler\r
+\r
+    //-div(style="width: 100%", [hidden]="!loading")  **Took this out because it would load quicker\r
+      mat-spinner(style="margin: auto", color="primary")\r
+\r
+    //- div(style="width: 100%;height: 40px;")\r
+\r
+  .row  \r
+    .col\r
+      ag-grid-angular.ag-theme-material(\r
+        style="width:100%; height: 600px",\r
+        [rowData]="rowData",\r
+        [columnDefs]="columns",\r
+        rowSelection="multiple",\r
+        [rowMultiSelectWithClick]="true",\r
+        (rowSelected)="onRowSelected($event)",\r
+        (gridReady)="onGridReady($event)",\r
+        [enableCellChangeFlash]="true",\r
+        (cellDoubleClicked)="navToDefinition($event)",\r
+        [singleClickEdit]="true"\r
+      )\r
+\r
+\r
+\r
+\r
+    //.card-body\r
+      .row\r
+        div.col-6\r
+          input.form-control.bg-light.mb-1([(ngModel)]="search.test_head_id", type="text", placeholder="Search...")\r
+        div.col-6\r
+          button.bg-primary.mbtn.pull-right.text-white.mb-1(mat-raised-button, (click)='createTestHead()') Create VTH\r
+      table.table.table-striped([mfData]='data', #mf='mfDataTable', [mfRowsOnPage]='5')\r
+        thead\r
+          tr\r
+            th(style='width: 20%')\r
+              mfDefaultSorter(by='name') Name\r
+            th(style='width: 50%')\r
+              mfDefaultSorter(by='creator') Creator\r
+            th(style='width: 10%')\r
+              mfDefaultSorter(by='date') Date \r
+            th(style='width: 20%') Options   \r
+        tbody\r
+          tr\r
+            td Ping Test Head\r
+            td Tiffany, Patrick \r
+            td 7/21/18\r
+            td \r
+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='View', (click)='viewTestHead(null)') \r
+                i.fa.fa-eye\r
+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='Edit', (click)='editTestHead()')\r
+                i.fa.fa-pencil\r
+              button.mbtn.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteTestHead()')\r
+                i.fa.fa-remove\r
diff --git a/otf-frontend/client/src/app/layout/tests/tests.component.scss b/otf-frontend/client/src/app/layout/tests/tests.component.scss
new file mode 100644 (file)
index 0000000..fa45135
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.mbtn:focus {\r
+       outline: none;\r
+}\r
+.mat-warn {\r
+    background-color: red;\r
+    color:red;\r
+}\r
+.bg-accent{\r
+    background-color: brown\r
+}\r
+.mat-mini-fab{\r
+    width: 30px !important;\r
+    height: 30px !important;\r
+    line-height: 10px !important;\r
+}\r
+.dropdown-toggle::after {\r
+    display:none;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/tests/tests.component.spec.ts b/otf-frontend/client/src/app/layout/tests/tests.component.spec.ts
new file mode 100644 (file)
index 0000000..9ca1b08
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestsComponent } from './tests.component';\r
+\r
+describe('TestsComponent', () => {\r
+  let component: TestsComponent;\r
+  let fixture: ComponentFixture<TestsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/tests/tests.component.ts b/otf-frontend/client/src/app/layout/tests/tests.component.ts
new file mode 100644 (file)
index 0000000..6b0019e
--- /dev/null
@@ -0,0 +1,535 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewContainerRef, ViewChild, AfterContentInit, OnDestroy } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { routerTransition } from '../../router.animations';\r
+import { ListService } from '../../shared/services/list.service';\r
+import { Router } from '@angular/router';\r
+import { TestDefinitionService } from '../../shared/services/test-definition.service';\r
+import { TestInstanceService } from '../../shared/services/test-instance.service';\r
+import { MatTableDataSource } from '@angular/material/table';\r
+import { MatPaginator, MatDialog, MatSnackBar } from '@angular/material';\r
+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';\r
+import { CreateTestComponent } from '../onboarding/create-test/create-test.component';\r
+import { TestDefinitionModalComponent } from 'app/shared/modules/test-definition-modal/test-definition-modal.component';\r
+import { ViewWorkflowModalComponent } from 'app/shared/modules/view-workflow-modal/view-workflow-modal.component';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { TestInstanceModalComponent } from '../../shared/modules/test-instance-modal/test-instance-modal.component';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { CookieService } from "ngx-cookie-service";\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { appInitializerFactory } from '@angular/platform-browser/src/browser/server-transition';\r
+import { element } from '@angular/core/src/render3/instructions';\r
+import { GridOptionsWrapper, RowNode, initialiseAgGridWithAngular1 } from 'ag-grid-community';\r
+import { every } from 'rxjs/operators';\r
+import { Subscription } from 'rxjs';\r
+\r
+@Component({\r
+  selector: 'app-tests',\r
+  templateUrl: './tests.component.pug',\r
+  styleUrls: ['./tests.component.scss'],\r
+  animations: [routerTransition()]\r
+})\r
+export class TestsComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  public dataSource;\r
+  public displayedColumns: string[] = ['lock', 'name', 'description', 'id', 'processDefinitionKey', 'options'];\r
+  public resultsLength;\r
+  public loading = false;\r
+\r
+\r
+  public columns = [\r
+    \r
+    {headerName: 'Name', field: 'testName', sortable: true, filter: true, resizable: true, checkboxSelection:true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, width: 300},\r
+    {headerName: 'Description', field: 'testDescription', sortable: true, filter: true, resizable: true},\r
+    {headerName: 'Id', field: '_id', sortable: true, filter: true, resizable: true, editable: true},\r
+    {headerName: 'Process Definition key', field: 'processDefinitionKey', sortable: true, filter: true, resizable: true},\r
+    {headerName: '', field: 'disabled', cellRenderer: this.disabledIndicator, hide: false, width: 80}\r
+    \r
+  ];\r
+  public rowData;\r
+  \r
+  /*\r
+  public rowData =  [\r
+    { _id: '5cfe7e5d6f4e5d0040a3b235', testDescription: 'For testing', testName: "testflow", processDefinitionKey: "demo"},\r
+    { make: 'Ford', model: 'Mondeo', price: 32000 },\r
+    { make: 'Porsche', model: 'Boxter', price: 72000 }\r
+]; */\r
+\r
+  public hasSelectedRows = false;\r
+  public selectedSingleRow = false;\r
+  public selectedUnlockedRows = true;\r
+  public selectedLockedRows = false; \r
+\r
+  private gridApi;\r
+  private gridColumnApi;\r
+  private selectedRows = {};\r
+\r
+  @ViewChild(MatPaginator) paginator: MatPaginator;\r
+\r
+  constructor(private http: HttpClient,\r
+    private router: Router,\r
+    private viewRef: ViewContainerRef,\r
+    private testDefinition: TestDefinitionService,\r
+    private modal: MatDialog,\r
+    private snack: MatSnackBar,\r
+    private user: UserService,\r
+    private testInstanceService: TestInstanceService,\r
+    private cookie: CookieService,\r
+    private _groups: GroupService\r
+  ) { }\r
+  \r
+  ngOnInit() {\r
+\r
+    this.setComponentData(this._groups.getGroup());\r
+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {\r
+      this.setComponentData(group);\r
+    }));\r
+\r
+\r
+  }\r
+\r
+  ngOnDestroy() {\r
+    this.toDestroy.forEach(elem => elem.unsubscribe());\r
+  }\r
+\r
+  setComponentData(group) {\r
+    \r
+    if(!group){\r
+      return;\r
+    }\r
+\r
+    this.loading = true;\r
+\r
+    this.dataSource = new MatTableDataSource();\r
+    this.dataSource.paginator = this.paginator;\r
+\r
+\r
+  \r
+    this.testDefinition.find({\r
+      $limit: -1,\r
+      groupId: group['_id'],\r
+      $sort: {\r
+        createdAt: -1\r
+      },\r
+      $select: ['testName', 'testDescription', 'processDefinitionKey', 'bpmnInstances.isDeployed', 'disabled', 'groupId']\r
+    }).subscribe((list) => {\r
+      this.dataSource.data = list;\r
+      this.resultsLength = this.dataSource.data.length;\r
+      this.loading = false;\r
+      // Getting row data filled with list\r
+      this.rowData = list;\r
+\r
+\r
+\r
+      //console.log("This is the rowdata: "+ JSON.stringify(this.rowData[1]))\r
+      //this.rowData = [].concat.apply([], list);\r
+    })\r
+  \r
+   \r
+  }\r
+\r
+  applyFilter(filterValue: string) {\r
+    this.dataSource.filter = filterValue.trim().toLowerCase();\r
+  }\r
+//createInstance(element)\r
+  createInstance() {\r
+    \r
+\r
+    this.selectedRows = this.gridApi.getSelectedRows().map(({ _id, testName }) => ({_id, testName}));\r
+    \r
+    const create = this.modal.open(TestInstanceModalComponent, {\r
+      width: '90%',\r
+      data: {\r
+        td: this.selectedRows[0]._id//element._id\r
+      },\r
+      disableClose: true\r
+    });\r
+  }\r
+\r
+  create() {\r
+    let create = this.modal.open(TestDefinitionModalComponent, {\r
+      disableClose: true\r
+    });\r
+\r
+    create.afterClosed().subscribe(res => {\r
+      this.ngOnInit();\r
+    })\r
+  }\r
+\r
+\r
+  // view(td){\r
+  //   this.modal.open(ViewWorkflowModalComponent, {\r
+  //     width: '90%',\r
+  //     height: '70%',\r
+  //     maxWidth: '100%',\r
+  //     data: {\r
+  //       id: td._id\r
+  //     }\r
+  //   });\r
+  // }\r
+\r
+\r
+\r
+\r
+\r
+  deleteMultiple(){\r
+    for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){\r
+      this.delete(i);\r
+    }\r
+  }\r
+\r
+  delete(td) {\r
+\r
+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testName }) => ({_id, testName}));\r
+    const deleter = this.modal.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to delete ' + this.selectedRows[td].testName + '? Any test instances or executions using this test definition will no longer work.'\r
+      }\r
+    });\r
+\r
+    deleter.afterClosed().subscribe(result => {\r
+      if (result) {\r
+        this.testDefinition.delete(this.selectedRows[td]._id).subscribe(response => {\r
+          this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            duration: 1500,\r
+            data: {\r
+              message: 'Test definition was deleted'\r
+            }\r
+          })\r
+          //this.ngOnInit();\r
+          this.setComponentData(this._groups.getGroup());\r
+        });\r
+      }\r
+    });\r
+  }\r
+\r
+  edit() {\r
+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id }) => ({_id}));\r
+    var editor = this.modal.open(TestDefinitionModalComponent, {\r
+      disableClose: true,\r
+      data: {\r
+        testDefinitionId: this.selectedRows[0]._id\r
+      }\r
+    });\r
+  }\r
+\r
+  lockMultiple(){\r
+   \r
+    for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){\r
+      this.lock(i);\r
+    }\r
+\r
+  }\r
+\r
+\r
+  lock(td) {\r
+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testName, groupId,  }) => ({_id, testName, groupId}));\r
+\r
+    let user = JSON.parse(this.cookie.get('currentUser'));\r
+    let isAdmin = false;\r
+    for (let i = 0; i < user.groups.length; i++) {\r
+      if (this.selectedRows[td].groupId === user.groups[i].groupId) {\r
+        if (user.groups[i].permissions.includes("admin")) {\r
+          isAdmin = true;\r
+        }\r
+      }\r
+    }\r
+    user = '';\r
+    if (!isAdmin) {\r
+      this.modal.open(AlertModalComponent, {\r
+        width: '250px',\r
+        data: {\r
+          type: 'alert',\r
+          message: 'You do not have the correct permissions to lock/unlock test definitions.'\r
+        }\r
+      })\r
+      return;\r
+    }\r
+    this.modal.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to lock ' + this.selectedRows[td].testName + '? All test instances using this test definition will be locked and no more instances can be created until unlocked.'\r
+      }\r
+    }).afterClosed().subscribe((result) => {\r
+      if (result) {\r
+        let testDef = {\r
+          '_id': this.selectedRows[td]._id,\r
+          'disabled': true\r
+        }\r
+        this.testDefinition.patch(testDef).subscribe((res) => {\r
+          this.selectedRows[td].disabled = true;\r
+          this.testInstanceService.find({ $limit: -1, testDefinitionId: this.selectedRows[td]._id }).subscribe((result) => {\r
+            \r
+            \r
+\r
+            if (result['length']) {\r
+              for (let i = 0; i < result['length']; i++) {\r
+                let ti = {\r
+                  '_id': null,\r
+                  'disabled': true\r
+                }\r
+                ti._id = result[i]._id;\r
+                ti.disabled = true;\r
+                let temp = ti;\r
+              \r
+                this.testInstanceService.patch(ti).subscribe((results) => {\r
+                 \r
+                  this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                    duration: 1500,\r
+                    data: {\r
+                      message: 'Test Instance ' + results['testInstanceName'] + ' was locked'\r
+                    }\r
+                  })\r
+                });\r
+              }\r
+            } else {\r
+              let ti = {\r
+                '_id': null,\r
+                'disabled': true\r
+              }\r
+              ti._id = result['_id'];\r
+              this.testInstanceService.patch(ti).subscribe((results) => {\r
+                this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                  duration: 1500,\r
+                  data: {\r
+                    message: 'Test Instance ' + results['testInstanceName'] + ' was locked'\r
+                  }\r
+                })\r
+              });;\r
+            }\r
+          });\r
+          this.setComponentData(this._groups.getGroup());\r
+        }, (error) => {\r
+          this.modal.open(AlertModalComponent, {\r
+            width: '250px',\r
+            data: {\r
+              type: "alert",\r
+              message: 'Test Definition could not be locked.'\r
+            }\r
+          })\r
+        });\r
+\r
+      }\r
+    })\r
+  }\r
+\r
+\r
+  updateData(){\r
+    \r
+    this.setComponentData(this._groups.getGroup());\r
+  }\r
+\r
+  unlockMultiple() {\r
+    for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){\r
+      this.unlock(i);\r
+    }\r
+  }\r
+//unlock multiple and loop through single unlock\r
+  unlock(td) {\r
+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testName, groupId,  }) => ({_id, testName, groupId}));\r
+    let user = JSON.parse(this.cookie.get('currentUser'));\r
+    let isAdmin = false;\r
+    for (let i = 0; i < user.groups.length; i++) {\r
+      if (this.selectedRows[td].groupId === user.groups[i].groupId) {\r
+        if (user.groups[i].permissions.includes("admin")) {\r
+          isAdmin = true;\r
+        }\r
+      }\r
+    }\r
+    user = '';\r
+    if (!isAdmin) {\r
+      this.modal.open(AlertModalComponent, {\r
+        width: '250px',\r
+        data: {\r
+          type: 'alert',\r
+          message: 'You do not have the correct permissions to lock/unlock test definitions.'\r
+        }\r
+      })\r
+      return;\r
+    }\r
+\r
+    this.modal.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to unlock ' + td.testName + '? All test instances using this test definition will be unlocked as well.'\r
+      }\r
+    }).afterClosed().subscribe((result) => {\r
+      if (result) {\r
+        let testDef = {\r
+          '_id': this.selectedRows[td]._id,\r
+          'disabled': false\r
+        }\r
+        this.testDefinition.patch(testDef).subscribe((res) => {\r
+          this.selectedRows[td].disabled = false;\r
+          this.testInstanceService.find({ $limit: -1, testDefinitionId: this.selectedRows[td]._id }).subscribe((result) => {\r
+           \r
+            // console.log(result);\r
+            if (result['length']) {\r
+              for (let i = 0; i < result['length']; i++) {\r
+                let ti = {\r
+                  '_id': null,\r
+                  'disabled': false\r
+                }\r
+                ti._id = result[i]._id;\r
+                ti.disabled = false;\r
+                this.testInstanceService.patch(ti).subscribe((results) => {\r
+                  this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                    duration: 1500,\r
+                    data: {\r
+                      message: 'Test Instance ' + results['testInstanceName'] + ' was unlocked'\r
+                    }\r
+                  })\r
+                });\r
+              }\r
+            } else {\r
+              let ti = {\r
+                '_id': null,\r
+                'disabled': false\r
+              }\r
+              ti._id = result['_id'];\r
+              \r
+              this.testInstanceService.patch(ti).subscribe((results) => {\r
+                this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                  duration: 1500,\r
+                  data: {\r
+                    message: 'Test Instance ' + results['testInstanceName'] + ' was unlocked'\r
+                  }\r
+                })\r
+              });;\r
+            }\r
+          });\r
+          this.setComponentData(this._groups.getGroup());\r
+        }, (error) => {\r
+          this.modal.open(AlertModalComponent, {\r
+            width: '250px',\r
+            data: {\r
+              type: "alert",\r
+              message: 'Test Definition could not be locked.'\r
+            }\r
+          })\r
+        });\r
+\r
+      }\r
+    })\r
+  }\r
+\r
+\r
+  isDeployed(element) {\r
+    let deployed = false;\r
+    if (element.bpmnInstances) {\r
+      element.bpmnInstances.forEach(elem => {\r
+        if (elem.isDeployed) {\r
+          deployed = true;\r
+        }\r
+      });\r
+    }\r
+    return deployed;\r
+  }\r
+\r
+\r
+\r
+  onRowSelected(event){\r
+\r
+    this.selectedRows = this.gridApi.getSelectedRows().map(({ _id, disabled  }) => ({ _id, disabled}));\r
+\r
+    if(event.api.getSelectedNodes().length > 0){\r
+      this.hasSelectedRows = true;\r
+\r
+      //Checks for all Unlocked rows\r
+      for (let i = 0; i < event.api.getSelectedNodes().length; i++ )\r
+      {\r
+\r
+        if(!this.selectedRows[i].disabled)\r
+        {\r
+          this.selectedUnlockedRows = true;\r
+        }\r
+        else{\r
+          this.selectedUnlockedRows = false;\r
+          break;\r
+        }\r
+      }\r
+\r
+      //Checks for all Locked rows\r
+      for (let i = 0; i < event.api.getSelectedNodes().length; i++ )\r
+      {\r
+\r
+        if(this.selectedRows[i].disabled)\r
+        {\r
+          this.selectedLockedRows = true;\r
+        }\r
+        else{\r
+          this.selectedLockedRows = false;\r
+          break;\r
+        }\r
+      }\r
+\r
+\r
+\r
+\r
+\r
+    }\r
+    else{\r
+      this.hasSelectedRows = false;\r
+      this.selectedLockedRows = false;\r
+      this.selectedUnlockedRows = true; \r
+\r
+    }\r
+    //One Row was selected\r
+    if((event.api.getSelectedNodes().length == 1)){\r
+      this.selectedSingleRow = true;\r
+     \r
+    }else{\r
+      this.selectedSingleRow = false;\r
+    }\r
+\r
+  }\r
+\r
+  onGridReady(params){\r
+    this.gridApi = params.api;\r
+    \r
+    this.gridColumnApi = params.columnApi;\r
+\r
+    //auto size the column widths\r
+    this.gridColumnApi.autoSizeColumns(['name']);\r
+  }\r
+\r
+  disabledIndicator(params){\r
+    if (params.value){\r
+      return `<mat-icon class="mat-icon mat-icon-no-color" role="img" >\r
+       locked</mat-icon>`;   \r
+    }\r
+  }\r
+\r
+\r
+\r
+\r
+  navToDefinition(event){\r
+    this.router.navigate(['/test-definitions', event.data._id]);\r
+  }\r
+\r
+  testDefinitionModeler(){\r
+    this.router.navigate(['/modeler'], {queryParams: {testDefinitionId: this.selectedRows[0]._id}});\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/tests/tests.module.spec.ts b/otf-frontend/client/src/app/layout/tests/tests.module.spec.ts
new file mode 100644 (file)
index 0000000..baebc53
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestsModule } from './tests.module';\r
+\r
+describe('TestsModule', () => {\r
+  let testsModule: TestsModule;\r
+\r
+  beforeEach(() => {\r
+    testsModule = new TestsModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(testsModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/tests/tests.module.ts b/otf-frontend/client/src/app/layout/tests/tests.module.ts
new file mode 100644 (file)
index 0000000..b1f5aa4
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {CommonModule} from '@angular/common';\r
+import {TestsRoutingModule} from './tests-routing.module';\r
+import {TestsComponent} from './tests.component';\r
+import {\r
+    MAT_DIALOG_DATA,\r
+    MatButtonModule,\r
+    MatFormFieldModule,\r
+    MatInputModule,\r
+    MatPaginatorModule,\r
+    MatSnackBarModule,\r
+    MatTableModule,\r
+    MatTooltipModule,\r
+    MatProgressSpinnerModule,\r
+    MatIconModule,\r
+    MatDatepickerModule\r
+} from '@angular/material';\r
+import {PageHeaderModule} from '../../shared';\r
+import {FilterPipeModule} from 'ngx-filter-pipe';\r
+import {FormsModule} from '@angular/forms';\r
+import {TestHeadModalModule} from '../../shared/modules/test-head-modal/test-head-modal.module';\r
+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';\r
+import {TestDefinitionModalModule} from 'app/shared/modules/test-definition-modal/test-definition-modal.module';\r
+import {CreateTestModule} from '../onboarding/create-test/create-test.module';\r
+import {ViewWorkflowModalModule} from 'app/shared/modules/view-workflow-modal/view-workflow-modal.module';\r
+import { CreateTestInstanceFormModule } from '../../shared/modules/create-test-instance-form/create-test-instance-form.module';\r
+import { TestInstanceModalModule } from '../../shared/modules/test-instance-modal/test-instance-modal.module';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { AgGridModule } from 'ag-grid-angular';\r
+import { TestDefinitionDetailsComponent } from './test-definition-details/test-definition-details.component';\r
+import {MatCardModule} from '@angular/material/card';\r
+import { DashboardModule } from '../dashboard/dashboard.module';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        TestsRoutingModule,\r
+        PageHeaderModule,\r
+        FormsModule,\r
+        FilterPipeModule,\r
+        MatButtonModule,\r
+        MatTableModule,\r
+        MatFormFieldModule,\r
+        MatInputModule,\r
+        MatPaginatorModule,\r
+        TestHeadModalModule,\r
+        TestInstanceModalModule,\r
+        AlertModalModule,\r
+        TestDefinitionModalModule,\r
+        CreateTestModule,\r
+        TestDefinitionModalModule,\r
+        MatTooltipModule,\r
+        ViewWorkflowModalModule,\r
+        MatSnackBarModule,\r
+        MatProgressSpinnerModule,\r
+        NgbModule,\r
+        AgGridModule.withComponents([]),\r
+        MatIconModule,\r
+        MatCardModule,\r
+        DashboardModule,\r
+        MatDatepickerModule\r
+    ],\r
+    declarations: [TestsComponent, TestDefinitionDetailsComponent],\r
+    providers: [{provide: MAT_DIALOG_DATA, useValue: {}}]\r
+})\r
+export class TestsModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management-routing.module.ts b/otf-frontend/client/src/app/layout/user-management/user-management-routing.module.ts
new file mode 100644 (file)
index 0000000..64eaf38
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { UserManagementComponent } from './user-management.component';\r
+\r
+\r
+const routes: Routes = [{\r
+    path:'', component: UserManagementComponent}];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class UserManagementRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management.component.pug b/otf-frontend/client/src/app/layout/user-management/user-management.component.pug
new file mode 100644 (file)
index 0000000..5e75101
--- /dev/null
@@ -0,0 +1,86 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  app-page-header([heading]="'User Management'", [icon]="'fa-edit'")\r
+\r
+  .card-mb-12\r
+    .pull-left\r
+      mat-form-field\r
+        input(matInput, name="filter", value="{{filterString}}", (keyup)="applyFilter($event.target.value)", placeholder="Filter")\r
+\r
+    div(style="width: 100%", [hidden]="!loading")\r
+      mat-spinner(style="margin: auto", color="primary")\r
+\r
+    table.mat-elevation-z8(mat-table, *ngIf="dataSource.data && dataSource.data.length > 0", [dataSource]="dataSource", style="width: 100%", [hidden]="loading")\r
+\r
+      ng-container(matColumnDef="lastName")\r
+        th(mat-header-cell, *matHeaderCellDef) lastName\r
+        td(mat-cell, *matCellDef="let element") {{ element.lastName }}\r
+\r
+      ng-container(matColumnDef="firstName")\r
+        th(mat-header-cell, *matHeaderCellDef) First Name\r
+        td(mat-cell, *matCellDef="let element") {{ element.firstName }}\r
+\r
+      ng-container(matColumnDef="email")\r
+        th(mat-header-cell, *matHeaderCellDef) Email\r
+        td(mat-cell, *matCellDef="let element") {{ element.email}}\r
+      \r
+      ng-container(matColumnDef="addGroups")\r
+        th(mat-header-cell, *matHeaderCellDef) Add to Group\r
+        td(mat-cell, *matCellDef="let element")\r
+          .dropdown(ngbDropdown, autoClose="outside", (openChange)="dropdownChange()", placement="left-top")\r
+            button(mat-mini-fab, color="primary", ngbDropdownToggle, (click)="null")\r
+              i.fa.fa-caret-down\r
+            .dropdown-menu(ngbDropdownMenu)\r
+              h4.mb-2.ml-1(style="font-weight: bold;") Change Groups\r
+              input.ml-1(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.groupName')\r
+              div(style="max-height: 300px; overflow-y: scroll")\r
+                .px-4.py-3\r
+                  .mr-2.ml-2(*ngFor="let group of groups | filterBy:search")\r
+                    mat-checkbox((change)="addRemoveGroupList(element, group._id, $event)", [(ngModel)]="element[group._id]") {{group.groupName}} \r
+              div(style="text-align: center")            \r
+                button.primary.mr-1(mat-raised-button, [disabled]= "!element.groupsToAddRemove || element.groupsToAddRemove.length <= 0", aria-label='Edit', color="primary", (click)='addGroups(element)') Add\r
+                button(mat-raised-button, [disabled]= "!element.groupsToAddRemove || element.groupsToAddRemove.length <= 0", color="warn", (click)='removeGroups(element)')  Remove\r
+\r
+              //- a.dropdown-item(*ngFor="let group of groups", (click)='addGroupsList(element, group._id)')\r
+              //-   span.pl-1 {{group.groupName}}\r
+              //-   i.fa.fa-check(*ngIf='element.groupsToAdd !== undefined && element.groupsToAdd.includes(group._id)')\r
+                \r
+              \r
+                \r
+            //- mat-select.mr-1((selectionChange)="onChange(element)", style="width: 30%; background: #80808066",[(ngModel)]="element['groupToAddRemove']")\r
+            //-   mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}\r
+            //- button.mr-1(mat-mini-fab, aria-label='Edit', color="primary", (click)='addGroup(element)')\r
+            //-   i.fa.fa-plus\r
+            //- button.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='removeGroup(element)')\r
+            //-   i.fa.fa-remove\r
+          \r
+      ng-container(matColumnDef="isVerified")\r
+        th(mat-header-cell, *matHeaderCellDef) Verified\r
+        td(mat-cell, *matCellDef="let element") {{element.isVerified ? "Yes" : "No"}}\r
+\r
+      ng-container(matColumnDef="enabled")\r
+        th(mat-header-cell, *matHeaderCellDef) Enabled\r
+        td(mat-cell, *matCellDef="let element")\r
+            mat-slide-toggle([(ngModel)]="element.enabled", "color"="primary", (input)="enableUser($event, element)")\r
+      //mat-slide-toggle([checked]="element.enabled? true : false", "color"="primary", (input)="enableUser($event, element)")\r
+\r
+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")\r
+\r
+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")\r
+\r
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management.component.scss b/otf-frontend/client/src/app/layout/user-management/user-management.component.scss
new file mode 100644 (file)
index 0000000..e177191
--- /dev/null
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.mat-mini-fab{\r
+    width: 30px !important;\r
+    height: 30px !important;\r
+    line-height: 10px !important;\r
+}\r
+\r
+.dropdown-toggle::after {\r
+    display:none;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management.component.spec.ts b/otf-frontend/client/src/app/layout/user-management/user-management.component.spec.ts
new file mode 100644 (file)
index 0000000..8dea47b
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { UserManagementComponent } from './user-management.component';\r
+\r
+describe('UserManagementComponent', () => {\r
+  let component: UserManagementComponent;\r
+  let fixture: ComponentFixture<UserManagementComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ UserManagementComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(UserManagementComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management.component.ts b/otf-frontend/client/src/app/layout/user-management/user-management.component.ts
new file mode 100644 (file)
index 0000000..4ce454f
--- /dev/null
@@ -0,0 +1,347 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {Component, OnInit, ViewContainerRef, ViewChild} from '@angular/core';\r
+import {ActivatedRoute, Router} from '@angular/router';\r
+import { MatPaginator, MatDialog, MatSnackBar } from '@angular/material';\r
+import { MatTableDataSource } from '@angular/material/table';\r
+import {HttpClient} from "@angular/common/http";\r
+import {UserService} from "../../shared/services/user.service";\r
+import { routerTransition } from '../../router.animations';\r
+import { ListService } from '../../shared/services/list.service';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';\r
+import * as organizeGroups from '../../../../../server/src/feathers/hooks/permissions/get-permissions';\r
+import { CookieService } from 'ngx-cookie-service';\r
+\r
+@Component({\r
+  selector: 'app-user-management',\r
+  templateUrl: './user-management.component.pug',\r
+  styleUrls: ['./user-management.component.scss'],\r
+  animations: [routerTransition()]\r
+\r
+})\r
+export class UserManagementComponent implements OnInit {\r
+\r
+    public dataSource;\r
+    public displayedColumns: string[] = ['lastName', 'firstName', 'email', 'addGroups', 'isVerified', 'enabled'];\r
+    public resultsLength;\r
+    public loading = false;\r
+    public filterString = "";\r
+    public groups;\r
+    public search;\r
+    public currentUser;\r
+    \r
+    @ViewChild(MatPaginator) paginator: MatPaginator;\r
+\r
+\r
+    constructor(private http: HttpClient,\r
+        private router: Router,\r
+        private viewRef: ViewContainerRef,\r
+        private list: ListService,\r
+        private modal: MatDialog,\r
+        private snack: MatSnackBar,\r
+        private user: UserService,\r
+        private route: ActivatedRoute,\r
+        private groupService: GroupService,\r
+        private cookie: CookieService\r
+    ) { }\r
+\r
+    ngOnInit() {\r
+        this.loading = true;\r
+        this.groups = [];\r
+        this.search = {};\r
+        this.search.groupName = '';\r
+        this.currentUser = JSON.parse(this.cookie.get('currentUser'));\r
+        this.groupService.find({$limit: -1}).subscribe((result) => {\r
+            if(result){\r
+                this.groups = organizeGroups(this.currentUser, result);\r
+                \r
+            }\r
+        })\r
+        this.route.queryParamMap.subscribe(queryParams => {\r
+            this.filterString = queryParams.get("filter");\r
+            if(!this.filterString){\r
+                this.filterString = "";\r
+            }\r
+        });\r
+        this.list.createList('td');\r
+        //["$limit=-1", "$sort[createdAt]=-1", "$select[]=lastName", "$select[]=firstName", "$select[]=email", "$select[]=isVerified", "$select[]=enabled"]\r
+        this.user.find({\r
+            $limit: -1,\r
+            $sort: {\r
+                createdAt: -1,\r
+            },\r
+            $select: ['lastName', 'firstName', 'email', 'isVerified', 'enabled', 'groups']\r
+        }).subscribe((list) => {\r
+            this.list.changeMessage('td', list);\r
+            this.loading = false;\r
+\r
+        });\r
+\r
+        this.dataSource = new MatTableDataSource();\r
+        this.dataSource.paginator = this.paginator;\r
+\r
+        this.list.listMap['td'].currentList.subscribe((list) =>{\r
+            if(list){\r
+                this.dataSource.data = list;\r
+                this.resultsLength = this.dataSource.data.length;\r
+                this.applyFilter(this.filterString)\r
+            }\r
+        });\r
+\r
+    }\r
+\r
+    applyFilter(filterValue: string) {\r
+        this.dataSource.filter = filterValue.trim().toLowerCase();\r
+    }\r
+\r
+    applyGroupFilter(filterValue: string){\r
+        this.groups.filter = filterValue.trim().toLowerCase();\r
+    }\r
+\r
+    dropdownChange(){\r
+        this.search.groupName = '';\r
+        for(let i in this.groups){\r
+            this.groups[i].selected = false;\r
+        }\r
+        \r
+    }\r
+\r
+    \r
+    addRemoveGroupList(element, groupId, event){\r
+        if(event.checked){\r
+            if (element.groupsToAddRemove){\r
+                element.groupsToAddRemove.push(groupId);\r
+            }else{\r
+                element.groupsToAddRemove = [];\r
+                element.groupsToAddRemove.push(groupId);\r
+            }\r
+        }else{\r
+            if(element.groupsToAddRemove){\r
+                let temp = element.groupsToAddRemove.indexOf(groupId)\r
+                if(temp >= 0)\r
+                    element.groupsToAddRemove.splice(temp, 1);\r
+            }\r
+        }\r
+        \r
+    }\r
+\r
+    removeGroups(user){\r
+        this.modal.open(AlertModalComponent, {\r
+            width: "250px",\r
+            data: {\r
+                type: "confirmation",\r
+                message: "Are you sure you want to remove " + user.firstName + " " + user.lastName + " from groups?"\r
+            }\r
+        }).afterClosed().subscribe((results) => {\r
+            if(results === undefined){\r
+                return;\r
+            }\r
+            if(results){\r
+                for(let i in user.groupsToAddRemove){\r
+                    user[user.groupsToAddRemove[i]] = false;\r
+                    let index = this.groups.findIndex(function(group){ return group._id == user.groupsToAddRemove[i]; })\r
+                    if(index >= 0 && this.groups[index].members){\r
+                        let memberIndex = this.groups[index].members.findIndex(function(member){return member.userId.toString() == user._id.toString()})\r
+                        if(memberIndex >= 0){\r
+                            this.groups[index].members.splice(memberIndex, 1);\r
+                            let groupPatch = {\r
+                                _id : this.groups[index]._id,\r
+                                members: this.groups[index].members\r
+                            }\r
+                            this.groupService.patch(groupPatch).subscribe((res) => {\r
+                                let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' removed from group!';\r
+                                this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                                    duration: 1500,\r
+                                    data: {\r
+                                        message: snackMessage\r
+                                    }\r
+                                });\r
+                            });   \r
+                        }\r
+                    }\r
+                  \r
+                }\r
+            }else{\r
+                return;\r
+            }\r
+            // let userPatch = {\r
+            //     _id : user._id,\r
+            //     groups: user.groups\r
+            // };\r
+\r
+            // this.user.patch(userPatch).subscribe((res) => {\r
+            //     let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' removed from group!';\r
+            //     this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            //         duration: 1500,\r
+            //         data: {\r
+            //             message: snackMessage\r
+            //         }\r
+            //     })\r
+            // });\r
+            user.groupsToAddRemove = [];\r
+            \r
+        });\r
+    }\r
+    //add "Change Groups" header to management dropdown\\r
+    addGroups(user){\r
+        this.modal.open(AlertModalComponent, {\r
+            width: "250px",\r
+            data: {\r
+                type: "userAdmin",\r
+                message: "Would you like to add as group user or group admin?"\r
+            }\r
+        }).afterClosed().subscribe((results) => {\r
+            if(results === undefined){\r
+                return;\r
+            }\r
+            if(results){\r
+                for(let i in user.groupsToAddRemove){\r
+                    user[user.groupsToAddRemove[i]] = false;\r
+                    let groupPatch = {\r
+                        _id : user.groupsToAddRemove[i],\r
+                        $push: { members: { userId : user._id, roles: ["admin"]}}\r
+                    }\r
+                    \r
+\r
+                    let index = this.groups.findIndex(function(group){ return group._id == user.groupsToAddRemove[i]; })\r
+                    if(index >= 0 && this.groups[index].members){\r
+                        let memberIndex = this.groups[index].members.findIndex(function(member){return member.userId.toString() == user._id.toString()});\r
+                        \r
+                        if(memberIndex >= 0 && !this.groups[index].members[memberIndex]["roles"].includes("admin")){\r
+                            groupPatch = this.groups[index];\r
+                            groupPatch["members"][memberIndex].roles.push("admin");\r
+                        }else if (memberIndex < 0) {\r
+                            groupPatch = {\r
+                                _id : user.groupsToAddRemove[i],\r
+                                $push: { members: { userId : user._id, roles: ["admin"]}}\r
+                            }\r
+                        }else{\r
+                            let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' already group admin!';\r
+                            this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                                duration: 1500,\r
+                                data: {\r
+                                    message: snackMessage\r
+                                }\r
+                            });\r
+                            continue;\r
+                        }\r
+                    }\r
+                    this.groupService.patch(groupPatch).subscribe((res) => {\r
+                        let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' added to group!';\r
+                        this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                            duration: 1500,\r
+                            data: {\r
+                                message: snackMessage\r
+                            }\r
+                        });\r
+                    });   \r
+                   \r
+                }\r
+            }else{\r
+                for(let i in user.groupsToAddRemove){\r
+                    user[user.groupsToAddRemove[i]] = false;\r
+                    let groupPatch = {\r
+                        _id : user.groupsToAddRemove[i],\r
+                        $push: { members: { userId : user._id, roles: [""]}}\r
+                    }\r
+                    \r
+\r
+                    let index = this.groups.findIndex(function(group){ return group._id == user.groupsToAddRemove[i]; })\r
+                    if(index >= 0 && this.groups[index].members){\r
+                        let memberIndex = this.groups[index].members.findIndex(function(member){return member.userId == user.groupsToAddRemove[i]})\r
+                        if(memberIndex >= 0 ){\r
+                            if( this.groups[index].members[memberIndex].roles.includes("admin")){\r
+                                groupPatch = this.groups[index];\r
+                                let adminIndex = groupPatch["members"][memberIndex].roles.findIndex(function(perm){return perm.toLowerCase() == "admin";});\r
+                                groupPatch["members"][memberIndex].roles.splice(adminIndex, 1);\r
+                            }else{\r
+                                return;\r
+                            }\r
+                        }else if (memberIndex < 0) {\r
+                            groupPatch = {\r
+                                _id : user.groupsToAddRemove[i],\r
+                                $push: { members: { userId : user._id, roles: [""]}}\r
+                            }\r
+                        }\r
+                    }\r
+                    this.groupService.patch(groupPatch).subscribe((res) => {\r
+                        let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' added to group!';\r
+                        this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                            duration: 1500,\r
+                            data: {\r
+                                message: snackMessage\r
+                            }\r
+                        });\r
+                    });  \r
+                }\r
+            }\r
+            // let userPatch = {\r
+            //     _id : user._id,\r
+            //     groups: user.groups\r
+            // };\r
+\r
+            // this.user.patch(userPatch).subscribe((res) => {\r
+            //     let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' added to group!';\r
+            //     this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            //         duration: 1500,\r
+            //         data: {\r
+            //             message: snackMessage\r
+            //         }\r
+            //     })\r
+            // });\r
+            user.groupsToAddRemove = [];\r
+           \r
+        });\r
+\r
+        \r
+    }\r
+\r
+    enableUser(event, element){\r
+        console.log(element)\r
+        let oldVal = element.enabled;\r
+        if(event.target.checked === element.enabled){\r
+            //console.log("same");\r
+            return\r
+        }\r
+        this.user.enableUser(element._id, event.target.checked).subscribe(\r
+            (result) => {\r
+                element.enabled = result['enabled'];\r
+                let snackMessage = 'Success. Set enabled to : ' + result['enabled'];\r
+                this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                    duration: 1500,\r
+                    data: {\r
+                        message: snackMessage\r
+                    }\r
+                })\r
+\r
+            },\r
+            (error) => {\r
+                element.enabled = oldVal;\r
+                let snackMessage = 'Could not set enabled to : ' + !oldVal;\r
+                this.snack.open(snackMessage, "Error", { duration: 1500 })\r
+\r
+\r
+            }\r
+\r
+        )\r
+\r
+\r
+    }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management.module.spec.ts b/otf-frontend/client/src/app/layout/user-management/user-management.module.spec.ts
new file mode 100644 (file)
index 0000000..529ddc8
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { UserManagementModule } from './user-management.module';\r
+\r
+describe('UserManagementModule', () => {\r
+  let userManagementModule: UserManagementModule;\r
+\r
+  beforeEach(() => {\r
+    userManagementModule = new UserManagementModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(userManagementModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/user-management/user-management.module.ts b/otf-frontend/client/src/app/layout/user-management/user-management.module.ts
new file mode 100644 (file)
index 0000000..01a84b6
--- /dev/null
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {CommonModule} from '@angular/common';\r
+import {UserManagementRoutingModule} from './user-management-routing.module';\r
+import {UserManagementComponent} from './user-management.component';\r
+import {\r
+    MAT_DIALOG_DATA,\r
+    MatButtonModule,\r
+    MatFormFieldModule,\r
+    MatInputModule,\r
+    MatPaginatorModule,\r
+    MatSnackBarModule,\r
+    MatSelectModule,\r
+    MatTableModule,\r
+    MatTooltipModule,\r
+    MatProgressSpinnerModule,\r
+    MatSlideToggleModule,\r
+    MatOptionModule,\r
+    MatCheckboxModule,\r
+    MatIconModule\r
+} from '@angular/material';\r
+import {PageHeaderModule} from '../../shared';\r
+import {FilterPipeModule} from 'ngx-filter-pipe';\r
+import {FormsModule} from '@angular/forms';\r
+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import {AlertSnackbarModule} from "../../shared/modules/alert-snackbar/alert-snackbar.module";\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        PageHeaderModule,\r
+        FormsModule,\r
+        FilterPipeModule,\r
+        MatButtonModule,\r
+        MatCheckboxModule,\r
+        MatTableModule,\r
+        MatFormFieldModule,\r
+        MatInputModule,\r
+        MatIconModule,\r
+        MatSlideToggleModule,\r
+        MatPaginatorModule,\r
+        AlertModalModule,\r
+        MatTooltipModule,\r
+        MatSnackBarModule,\r
+        MatSelectModule,\r
+        MatOptionModule,\r
+        MatProgressSpinnerModule,\r
+        NgbModule,\r
+        AlertSnackbarModule,\r
+        UserManagementRoutingModule\r
+    ],\r
+    declarations: [UserManagementComponent],\r
+    providers: [{provide: MAT_DIALOG_DATA, useValue: {}}]\r
+})\r
+export class UserManagementModule { }\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.pug b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.pug
new file mode 100644 (file)
index 0000000..cd6f182
--- /dev/null
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(#linechartdiv, [style.height]="height")\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.scss b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..af1de53
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestHeadExecutionsLineChartComponent } from './test-head-executions-line-chart.component';\r
+\r
+describe('TestHeadExecutionsLineChartComponent', () => {\r
+  let component: TestHeadExecutionsLineChartComponent;\r
+  let fixture: ComponentFixture<TestHeadExecutionsLineChartComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestHeadExecutionsLineChartComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestHeadExecutionsLineChartComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component.ts
new file mode 100644 (file)
index 0000000..2d4abf6
--- /dev/null
@@ -0,0 +1,190 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';\r
+import { Subscription } from 'rxjs';\r
+import { StatsService } from 'app/layout/components/stats/stats.service';\r
+import * as moment from 'moment';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+\r
+@Component({\r
+  selector: 'app-test-head-executions-line-chart',\r
+  templateUrl: './test-head-executions-line-chart.component.pug',\r
+  styleUrls: ['./test-head-executions-line-chart.component.scss']\r
+})\r
+export class TestHeadExecutionsLineChartComponent implements OnInit {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+\r
+  @ViewChild('linechartdiv') LineChartDiv: ElementRef;\r
+  @Input() height: string;\r
+  @Input() testHeadId;\r
+  @Output() total: EventEmitter<any> = new EventEmitter();\r
+\r
+  //public testDefinitionName = "Hello";\r
+  private chart: am4charts.XYChart;\r
+  private loadingIndicator;\r
+\r
+  constructor(private stats: StatsService) {\r
+  }\r
+\r
+  ngOnInit() {\r
+    \r
+\r
+    this.renderChart();\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {\r
+      this.setChartData();\r
+    }));\r
+\r
+  }\r
+\r
+  ngOnDestroy(){\r
+    //destory chart\r
+    this.chart.dispose();\r
+  }\r
+\r
+  //Sets count to 0 for any dates that dont have data\r
+  setupPoints(rawData) {\r
+    let formattedData = []; \r
+    let dayRange = moment(this.stats.filters.endDate).add(1, 'days').diff(moment(this.stats.filters.startDate), 'days');\r
+\r
+    for(let i = 0; i < dayRange; i++){\r
+      //find date in raw data\r
+      let d = rawData.find(e => moment(e.date).isSame(moment(this.stats.filters.startDate).add(i, 'days'), 'day'));\r
+      let count = 0;\r
+      if(d){\r
+        count = d.count;\r
+      }\r
+      formattedData.push({\r
+        date: moment(this.stats.filters.startDate).startOf('day').add(i, 'days').toDate(),\r
+        count: count\r
+      })\r
+    }\r
+\r
+    return formattedData;\r
+  }\r
+\r
+  showLoadingIndicator() {\r
+\r
+    //this.height = "380px";\r
+    if(!this.loadingIndicator){\r
+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+      this.loadingIndicator.background.fill = am4core.color("#fff");\r
+      this.loadingIndicator.background.fillOpacity = 0.8;\r
+      this.loadingIndicator.width = am4core.percent(100);\r
+      this.loadingIndicator.height = am4core.percent(100);\r
+\r
+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+      indicatorLabel.text = "Loading..";\r
+      indicatorLabel.align = "center";\r
+      indicatorLabel.valign = "middle";\r
+      indicatorLabel.fontSize = 18;\r
+      indicatorLabel.fontWeight = "bold";\r
+      indicatorLabel.dy = 50;\r
+\r
+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+      loadingImage.href = "/assets/images/equalizer.gif";\r
+      //loadingImage.dataSource = "/loading-pies.svg"\r
+      loadingImage.align = "center";\r
+      loadingImage.valign = "middle";\r
+      loadingImage.horizontalCenter = "middle";\r
+      loadingImage.verticalCenter = "middle";\r
+      loadingImage.scale = 3.0;\r
+    }else{\r
+      this.loadingIndicator.show();\r
+    }\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  setChartData() {\r
+    let data = [];\r
+    let total = 0;\r
+    this.stats.executionList.forEach(e => {\r
+      if (e.testHeadResults && e.testHeadResults.length > 0) {\r
+\r
+        e.testHeadResults.forEach((result, index) => {\r
+\r
+          if(result.testHeadId == this.testHeadId){\r
+            total++;\r
+            let dataIndex = data.findIndex(d => moment(d.date).isSame(result.startTime, 'day'));\r
+\r
+            if (dataIndex == -1) {\r
+              data.push({ date: moment(result.startTime).toDate(), count: 1 });\r
+            }else{\r
+              data[dataIndex].count++;\r
+            }\r
+            \r
+          }\r
+\r
+        })\r
+      }\r
+    })\r
+    \r
+    this.chart.data = this.setupPoints(data);\r
+    this.total.emit(total);\r
+    this.hideLoadingIndicator();\r
+  }\r
+\r
+  renderChart() {\r
+\r
+    if(this.chart){\r
+      this.chart.dispose();\r
+    }\r
+    this.chart = am4core.create(this.LineChartDiv.nativeElement, am4charts.XYChart);\r
+    this.chart.preloader.disabled = true;\r
+    this.showLoadingIndicator();\r
+\r
+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
+    dateAxis.fontSize = "10px";\r
+\r
+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.title.text = "Executions";\r
+    valueAxis.title.fontSize = "10px";\r
+\r
+    let series = this.chart.series.push(new am4charts.LineSeries());\r
+    series.dataFields.dateX = "date";\r
+    series.dataFields.valueY = "count";\r
+    series.strokeWidth = 3;\r
+\r
+    series.fillOpacity = .5;\r
+    // series.tensionX = 0.8;\r
+    series.sequencedInterpolation = false;\r
+    series.tooltipText = "{valueY.value}";\r
+\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+\r
+    this.chart.responsive.enabled = true;\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.pug b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.pug
new file mode 100644 (file)
index 0000000..fce6e86
--- /dev/null
@@ -0,0 +1,98 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+mat-card.mb-3\r
+    mat-card-header \r
+        mat-card-title \r
+            h4(*ngIf="testHead?.testHeadName") {{ testHead.testHeadName }} \r
+        mat-card-subtitle(style="margin-bottom: 0px")\r
+            div(*ngIf="testHead?.testHeadDescription") {{testHead.testHeadDescription }}\r
+    mat-card-content\r
+        .row(*ngIf="testHead")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testHead?.hostname")\r
+                    input(matInput, placeholder="Host Name", type="text", [value]="testHead.hostname", disabled, name="host")\r
+            .col-sm\r
+                mat-form-field(style="width:50px", *ngIf="testHead?.port") \r
+                    input(matInput, placeholder="Port", type="text", [value]="testHead.port", disabled, name="port")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testHead?.resourcePath") \r
+                    input(matInput, placeholder="Resource Path", type="text", [value]="testHead.resourcePath", disabled, name="path")\r
+            .col-sm\r
+                mat-form-field(*ngIf="testHead?.groupId")\r
+                    input(matInput, placeholder="Group", type="text", [value]="testHead.groupId", disabled, name="group")\r
+            .col-sm\r
+                mat-form-field(style="width:50px",*ngIf="testHead?.isPublic != undefined")\r
+                    input(matInput, placeholder="Is Public", type="text", [value]="testHead.isPublic", disabled, name="public")\r
+\r
+div(style="position: relative")\r
+  .row\r
+    .col-12\r
+      .pull-left\r
+        mat-form-field(style="width:110px")\r
+          input(matInput, [matDatepicker]="fromPicker", placeholder="From Date", [(ngModel)]="stats.filters.startDate")\r
+          mat-datepicker-toggle(matSuffix, [for]="fromPicker")\r
+          mat-datepicker(#fromPicker)\r
+        mat-form-field.ml-2(style="width:110px")\r
+          input(matInput, [matDatepicker]="toPicker", placeholder="To Date", [(ngModel)]="stats.filters.endDate")\r
+          mat-datepicker-toggle(matSuffix, [for]="toPicker")\r
+          mat-datepicker(#toPicker)\r
+        button.ml-2(mat-icon-button, (click)="getData()") \r
+          mat-icon arrow_forward\r
+          \r
+      .pull-right\r
+        mat-form-field\r
+          input(matInput, [ngModel]="totalExecutions", placeholder="Total Executions", disabled)\r
+\r
+  .row\r
+    .col-12\r
+      mat-card\r
+        mat-card-content\r
+          app-test-head-executions-line-chart(*ngIf="testHead", height="201px", [testHeadId]="testHead._id", (total)="setTotalExecutions($event)")\r
+\r
+//-   .row.mt-2\r
+//-     .col-lg-5\r
+//-       mat-card\r
+//-         mat-card-header\r
+//-           mat-card-title \r
+//-             h5 Test Results\r
+//-         mat-card-content\r
+//-           app-pie-chart(height="230px")\r
+    \r
+//-     .col-lg-7\r
+//-       mat-card\r
+//-         mat-card-header\r
+//-           mat-card-title \r
+//-             h5 Test Definition Usage\r
+//-         mat-card-content\r
+//-           app-test-definition-executions-bar-chart(height="230px")\r
+//-   .row.mt-2\r
+    \r
+//-     .col-lg-8\r
+//-       mat-card\r
+//-         mat-card-header\r
+//-           mat-card-title \r
+//-             h5 Virtual Test Head Executions\r
+//-         mat-card-content\r
+//-           app-test-head-executions-line-chart(height="230px")\r
+    \r
+//-     .col-lg-4\r
+//-       mat-card\r
+//-         mat-card-header\r
+//-           mat-card-title \r
+//-             h5 Virtual Test Head Usage\r
+//-         mat-card-content\r
+//-           app-test-head-execution-bar-chart(height="230px")
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.scss b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.spec.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.spec.ts
new file mode 100644 (file)
index 0000000..30b35bf
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { VirtualTestHeadDetailsComponent } from './virtual-test-head-details.component';\r
+\r
+describe('VirtualTestHeadDetailsComponent', () => {\r
+  let component: VirtualTestHeadDetailsComponent;\r
+  let fixture: ComponentFixture<VirtualTestHeadDetailsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ VirtualTestHeadDetailsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(VirtualTestHeadDetailsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.ts
new file mode 100644 (file)
index 0000000..4c1fe5c
--- /dev/null
@@ -0,0 +1,104 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import {ActivatedRoute} from "@angular/router";\r
+import {TestHead} from "app/shared/models/test-head.model";\r
+import {TestHeadService} from "app/shared/services/test-head.service";\r
+import { Subscription } from 'rxjs';\r
+import { StatsService } from 'app/layout/components/stats/stats.service';\r
+\r
+\r
+@Component({\r
+  selector: 'app-virtual-test-head-details',\r
+  templateUrl: './virtual-test-head-details.component.pug',\r
+  styleUrls: ['./virtual-test-head-details.component.scss']\r
+})\r
+export class VirtualTestHeadDetailsComponent implements OnInit {\r
+\r
+  private toDestroy : Array<Subscription> = [];\r
+  testHead : TestHead;\r
+  public totalExecutions;\r
+  constructor(\r
+    private route: ActivatedRoute, \r
+    private testHeadService : TestHeadService,\r
+    public stats: StatsService\r
+  ) { }\r
+\r
+  ngOnInit() {\r
+    this.toDestroy.push(this.route.params.subscribe(param => {\r
+      if(param.id){\r
+        this.toDestroy.push(this.testHeadService.get(param.id).subscribe(res => {\r
+          this.testHead = res as TestHead;\r
+          \r
+        }, err=>{\r
+          console.log(err);\r
+        }));\r
+\r
+        this.getData(param.id);\r
+      }\r
+    }));\r
+    \r
+  }\r
+\r
+  ngOnDestroy(){\r
+    this.toDestroy.forEach(e => {\r
+      e.unsubscribe()\r
+    });\r
+  }\r
+\r
+  getData(testHeadId?){\r
+    if(!testHeadId){\r
+      testHeadId = this.testHead._id\r
+    }\r
+\r
+    if(!testHeadId){\r
+      return;\r
+    }\r
+\r
+    this.stats.getDefaultData(1, {\r
+      'testHeadResults.testHeadId': testHeadId,\r
+      $select: [\r
+        'startTime',\r
+        'endTime',\r
+        "historicTestDefinition._id",\r
+        "historicTestDefinition.testName",\r
+        "historicTestInstance._id",\r
+        "historicTestInstance.testInstanceName",\r
+        "testHeadResults.startTime",\r
+        "testHeadResults.endTime",\r
+        "testHeadResults.testHeadName",\r
+        "testHeadResults.testHeadId",\r
+        "testHeadResults.testHeadGroupId",\r
+        "testHeadResults.statusCode",\r
+        'testResult'\r
+      ],\r
+      $limit: -1,\r
+      $sort: {\r
+        startTime: 1\r
+      },\r
+      startTime: {\r
+        $gte: this.stats.filters.startDate,\r
+        $lte: this.stats.filters.endDate\r
+      }\r
+    });\r
+  }\r
+\r
+\r
+  setTotalExecutions(event){\r
+    this.totalExecutions = event;\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads-routing.module.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads-routing.module.ts
new file mode 100644 (file)
index 0000000..3ca6d4f
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { VirtualTestHeadsComponent } from './virtual-test-heads.component';\r
+import { VirtualTestHeadDetailsComponent } from './virtual-test-head-details/virtual-test-head-details.component';\r
+\r
+const routes: Routes = [\r
+  { path:'', component: VirtualTestHeadsComponent },\r
+  { path:':id', component: VirtualTestHeadDetailsComponent}\r
+];\r
+\r
+@NgModule({\r
+  imports: [RouterModule.forChild(routes)],\r
+  exports: [RouterModule]\r
+})\r
+export class VirtualTestHeadsRoutingModule { }\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.pug b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.pug
new file mode 100644 (file)
index 0000000..2c46590
--- /dev/null
@@ -0,0 +1,80 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  app-page-header([heading]="'Virtual Test Heads'", [icon]="'fa-edit'")\r
+  \r
+  .card-mb-12 \r
+    .pull-left\r
+      mat-form-field\r
+        input(matInput, name="filter", (keyup)="applyFilter($event.target.value)", placeholder="Filter")\r
+    .pull-right\r
+      button(mat-raised-button, color="primary", (click)="createTestHead()") New\r
+\r
+    div(style="width: 100%", [hidden]="!loading")\r
+      mat-spinner(style="margin: auto", color="primary")\r
+    \r
+    table.mat-elevation-z8(mat-table, [dataSource]="dataSource", style="width: 100%", [hidden]="loading")\r
+\r
+      ng-container(matColumnDef="name")\r
+        th(mat-header-cell, *matHeaderCellDef) Name\r
+        td(mat-cell, *matCellDef="let element", [routerLink]="['/test-heads', element._id]") {{ element.testHeadName}}\r
+\r
+      ng-container(matColumnDef="description")\r
+        th(mat-header-cell, *matHeaderCellDef) Description\r
+        td(mat-cell, *matCellDef="let element", [routerLink]="['/test-heads', element._id]") {{ element.testHeadDescription}}\r
+\r
+      ng-container(matColumnDef="options")\r
+        th(mat-header-cell, *matHeaderCellDef) Options\r
+        td(mat-cell, *matCellDef="let element")\r
+          button.mr-3(mat-mini-fab, aria-label='Edit', color="primary", (click)='editTestHead(element)')\r
+            i.fa.fa-pencil\r
+          button.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteTestHead(element)')\r
+            i.fa.fa-remove\r
+\r
+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")\r
+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")\r
+\r
+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")\r
+\r
+    //.card-body\r
+      .row\r
+        div.col-6\r
+          input.form-control.bg-light.mb-1([(ngModel)]="search.test_head_id", type="text", placeholder="Search...")\r
+        div.col-6\r
+          button.bg-primary.mbtn.pull-right.text-white.mb-1(mat-raised-button, (click)='createTestHead()') Create VTH\r
+      table.table.table-striped([mfData]='data', #mf='mfDataTable', [mfRowsOnPage]='5')\r
+        thead\r
+          tr\r
+            th(style='width: 20%')\r
+              mfDefaultSorter(by='name') Name\r
+            th(style='width: 50%')\r
+              mfDefaultSorter(by='creator') Creator\r
+            th(style='width: 10%')\r
+              mfDefaultSorter(by='date') Date \r
+            th(style='width: 20%') Options   \r
+        tbody\r
+          tr\r
+            td Ping Test Head\r
+            td Tiffany, Patrick \r
+            td 7/21/18\r
+            td \r
+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='View', (click)='viewTestHead(null)') \r
+                i.fa.fa-eye\r
+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='Edit', (click)='editTestHead()')\r
+                i.fa.fa-pencil\r
+              button.mbtn.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteTestHead()')\r
+                i.fa.fa-remove\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.scss b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.scss
new file mode 100644 (file)
index 0000000..03c9b55
--- /dev/null
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.mbtn:focus {\r
+       outline: none;\r
+}\r
+.mat-warn {\r
+    background-color: red;\r
+    color:red;\r
+}\r
+.bg-accent{\r
+    background-color: brown\r
+}\r
+.mat-mini-fab{\r
+    width: 30px !important;\r
+    height: 30px !important;\r
+    line-height: 10px !important;\r
+}\r
+\r
+tr:hover {background-color: #ddd;}\r
+\r
+.card-header{\r
+    font-size: large;\r
+    font-family: "Arial Black", Gadget, sans-serif; \r
+}\r
+\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.spec.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.spec.ts
new file mode 100644 (file)
index 0000000..5b6b605
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { VirtualTestHeadsComponent } from './virtual-test-heads.component';\r
+\r
+describe('VirtualTestHeadsComponent', () => {\r
+  let component: VirtualTestHeadsComponent;\r
+  let fixture: ComponentFixture<VirtualTestHeadsComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ VirtualTestHeadsComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(VirtualTestHeadsComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.ts
new file mode 100644 (file)
index 0000000..0853862
--- /dev/null
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Output, Input, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { AppGlobals } from '../../app.global';\r
+import { routerTransition } from '../../router.animations';\r
+import { ListService } from '../../shared/services/list.service';\r
+import { Router } from '@angular/router';\r
+import { MatTableDataSource, MatPaginator, MatSort, MatDialog, MatSnackBar } from '@angular/material';\r
+import { TestHeadService } from '../../shared/services/test-head.service';\r
+import { TestHeadModalComponent } from '../../shared/modules/test-head-modal/test-head-modal.component';\r
+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';\r
+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { Subscription } from 'rxjs';\r
+\r
+@Component({\r
+  selector: 'app-virtual-test-heads',\r
+  templateUrl: './virtual-test-heads.component.pug',\r
+  providers: [AppGlobals],\r
+  styleUrls: ['./virtual-test-heads.component.scss'],\r
+  animations: [routerTransition()]\r
+})\r
+export class VirtualTestHeadsComponent implements OnInit, OnDestroy {\r
+\r
+  private toDestroy: Array<Subscription> = [];\r
+  public dataSource;\r
+  public displayedColumns: string[] = ['name', 'description', 'options'];\r
+  public resultsLength;\r
+  public loading = false;\r
+\r
+  @ViewChild(MatPaginator) paginator: MatPaginator;\r
+\r
+  constructor(private testHead: TestHeadService,\r
+    private router: Router,\r
+    private modal: MatDialog,\r
+    private snack: MatSnackBar,\r
+    private _groups: GroupService) { }\r
+\r
+  applyFilter(filterValue: string) {\r
+    this.dataSource.filter = filterValue.trim().toLowerCase();\r
+  }\r
+\r
+\r
+  ngOnInit() {\r
+    this.setComponentData(this._groups.getGroup());\r
+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {\r
+      this.setComponentData(group);\r
+    }));\r
+  }\r
+\r
+  ngOnDestroy(){\r
+    this.toDestroy.forEach(e => e.unsubscribe());\r
+  }\r
+\r
+  setComponentData(group) {\r
+    if(group){\r
+      \r
+      this.dataSource = new MatTableDataSource();\r
+      this.dataSource.paginator = this.paginator;\r
+\r
+      this.testHead.find({ $limit: -1, groupId: group['_id'], $sort: { createdAt: -1 } }).subscribe((list) => {\r
+        this.dataSource.data = list;\r
+        this.resultsLength = this.dataSource.data.length;\r
+        this.loading = false;\r
+      })\r
+    }\r
+  }\r
+\r
+  createTestHead() {\r
+    const create = this.modal.open(TestHeadModalComponent, {\r
+      width: '90%',\r
+      data: {\r
+        goal: 'create'\r
+      }\r
+    })\r
+\r
+    create.afterClosed().subscribe(result => {\r
+      this.ngOnInit();\r
+      // this.list.listMap['vth'].currentList.subscribe(x => {\r
+      //   this.dataSource.data = x;\r
+      //   this.resultsLength = this.dataSource.data.length;\r
+      // });\r
+    });\r
+  }\r
+\r
+\r
+  editTestHead(th) {\r
+    const edit = this.modal.open(TestHeadModalComponent, {\r
+      width: '90%',\r
+      data: {\r
+        goal: 'edit',\r
+        testHead: th\r
+      }\r
+    });\r
+\r
+    edit.afterClosed().subscribe(result => {\r
+      this.ngOnInit();\r
+    });\r
+  }\r
+\r
+  deleteTestHead(th) {\r
+    const deleter = this.modal.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to delete ' + th.testHeadName + '? There may be test definitions using this test head.'\r
+      }\r
+    });\r
+\r
+    deleter.afterClosed().subscribe(result => {\r
+      if (result) {\r
+        this.testHead.delete(th._id).subscribe(response => {\r
+          this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            duration: 1500,\r
+            data: {\r
+              message: 'Test Head Deleted'\r
+            }\r
+          });\r
+\r
+          this.ngOnInit();\r
+        });\r
+      }\r
+    });\r
+  }\r
+\r
+  navToTestHead(id){\r
+    this.router.navigate(['/test-heads', id]);\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.spec.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.spec.ts
new file mode 100644 (file)
index 0000000..1eeb733
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { VirtualTestHeadsModule } from './virtual-test-heads.module';\r
+\r
+describe('VirtualTestHeadsModule', () => {\r
+  let virtualTestHeadsModule: VirtualTestHeadsModule;\r
+\r
+  beforeEach(() => {\r
+    virtualTestHeadsModule = new VirtualTestHeadsModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(virtualTestHeadsModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.ts b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.ts
new file mode 100644 (file)
index 0000000..a394353
--- /dev/null
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {NgModule} from '@angular/core';\r
+import {CommonModule} from '@angular/common';\r
+\r
+import {VirtualTestHeadsRoutingModule} from './virtual-test-heads-routing.module';\r
+import {VirtualTestHeadsComponent} from './virtual-test-heads.component';\r
+import {PageHeaderModule} from '../../shared';\r
+import {FilterPipeModule} from 'ngx-filter-pipe';\r
+import {FormsModule} from '@angular/forms';\r
+import {\r
+    MAT_DIALOG_DATA,\r
+    MatButtonModule,\r
+    MatFormFieldModule,\r
+    MatInputModule,\r
+    MatPaginatorModule,\r
+    MatSnackBarModule,\r
+    MatProgressSpinnerModule,\r
+    MatDatepickerModule,\r
+    MatIconModule,\r
+    MatNativeDateModule\r
+} from '@angular/material';\r
+import {CreateTestHeadFormModule} from '../../shared/modules/create-test-head-form/create-test-head-form.module';\r
+import {MatTableModule} from '@angular/material/table';\r
+import {TestHeadModalModule} from '../../shared/modules/test-head-modal/test-head-modal.module';\r
+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';\r
+import {AlertSnackbarModule} from 'app/shared/modules/alert-snackbar/alert-snackbar.module';\r
+import { VirtualTestHeadDetailsComponent } from './virtual-test-head-details/virtual-test-head-details.component';\r
+import {MatDividerModule} from '@angular/material/divider';\r
+import {MatCardModule} from '@angular/material/card';\r
+import { LineChartComponent } from '../components/stats/line-chart/line-chart.component';\r
+import { DashboardModule } from '../dashboard/dashboard.module';\r
+import { TestHeadExecutionsLineChartComponent } from './virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule,\r
+        VirtualTestHeadsRoutingModule,\r
+        PageHeaderModule,\r
+        FormsModule,\r
+        FilterPipeModule,\r
+        CreateTestHeadFormModule,\r
+        MatButtonModule,\r
+        MatTableModule,\r
+        MatFormFieldModule,\r
+        MatInputModule,\r
+        MatPaginatorModule,\r
+        TestHeadModalModule,\r
+        AlertModalModule,\r
+        MatSnackBarModule,\r
+        AlertSnackbarModule,\r
+        MatProgressSpinnerModule,\r
+        MatDividerModule,\r
+        MatCardModule,\r
+        MatDatepickerModule,\r
+        MatNativeDateModule,\r
+        MatIconModule\r
+    ],\r
+    declarations: [VirtualTestHeadsComponent, VirtualTestHeadDetailsComponent, TestHeadExecutionsLineChartComponent],\r
+    entryComponents: [],\r
+    providers: [{provide: MAT_DIALOG_DATA, useValue: {}}, MatDatepickerModule]\r
+})\r
+export class VirtualTestHeadsModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/login/login-routing.module.ts b/otf-frontend/client/src/app/login/login-routing.module.ts
new file mode 100644 (file)
index 0000000..04ea965
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { LoginComponent } from './login.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '',\r
+        component: LoginComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class LoginRoutingModule {}\r
diff --git a/otf-frontend/client/src/app/login/login.component.html b/otf-frontend/client/src/app/login/login.component.html
new file mode 100644 (file)
index 0000000..7bb3328
--- /dev/null
@@ -0,0 +1,46 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<div class="login-page" [@routerTransition]>\r
+    <div class="row justify-content-md-center">\r
+        <div class="col-md-4">\r
+            <img src="assets/images/NetworkLogo.jpg" width="200px" class="user-avatar" />\r
+            <h1>Open Test Framework</h1>\r
+            <form #login="ngForm" (ngSubmit)="onLoggedin()" role="form">\r
+                <div class="form-content">\r
+                    <div class="form-group">\r
+                        <input type="text" [(ngModel)]="User.email" [ngModelOptions]="{standalone: true}" class="form-control input-underline input-lg" id="email" #email="ngModel" placeholder="Email">\r
+                    </div>\r
+                    \r
+\r
+                    <div class="form-group">\r
+                        <input type="password" [(ngModel)]="User.password" [ngModelOptions]="{standalone: true}" class="form-control input-underline input-lg" id="password"  placeholder="Password">\r
+                        \r
+                    </div>\r
+                    <div *ngIf="loginFailed && (email.dirty || email.touched)"\r
+                            class="alert-danger">\r
+                            <div>\r
+                                Username/Password is incorrect.\r
+                            </div>\r
+                    </div>\r
+                </div>\r
+                <button class="btn rounded-btn" type="submit" id="login"> Log in </button>\r
+                &nbsp;\r
+                <a class="btn rounded-btn" [routerLink]="['/signup']">Register</a>\r
+            </form>\r
+        </div>\r
+    </div>\r
+</div>\r
diff --git a/otf-frontend/client/src/app/login/login.component.scss b/otf-frontend/client/src/app/login/login.component.scss
new file mode 100644 (file)
index 0000000..215e85f
--- /dev/null
@@ -0,0 +1,112 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+$topnav-background-color: #222;\r
+:host {\r
+    display: block;\r
+}\r
+.login-page {\r
+    position: absolute;\r
+    top: 0;\r
+    left: 0;\r
+    right: 0;\r
+    bottom: 0;\r
+    overflow: auto;\r
+    background: $topnav-background-color;\r
+    text-align: center;\r
+    color: #fff;\r
+    padding: 3em;\r
+    .col-lg-4 {\r
+        padding: 0;\r
+    }\r
+    .input-lg {\r
+        height: 46px;\r
+        padding: 10px 16px;\r
+        font-size: 18px;\r
+        line-height: 1.3333333;\r
+        border-radius: 0;\r
+    }\r
+    .input-underline {\r
+        background: 0 0;\r
+        border: none;\r
+        box-shadow: none;\r
+        border-bottom: 2px solid rgba(255, 255, 255, 0.5);\r
+        color: #fff;\r
+        border-radius: 0;\r
+    }\r
+    .input-underline:focus {\r
+        border-bottom: 2px solid #fff;\r
+        box-shadow: none;\r
+    }\r
+    .rounded-btn {\r
+        -webkit-border-radius: 50px;\r
+        border-radius: 50px;\r
+        color: rgba(255, 255, 255, 0.8);\r
+        background: $topnav-background-color;\r
+        border: 2px solid rgba(255, 255, 255, 0.8);\r
+        font-size: 18px;\r
+        line-height: 40px;\r
+        padding: 0 25px;\r
+    }\r
+    .rounded-btn:hover,\r
+    .rounded-btn:focus,\r
+    .rounded-btn:active,\r
+    .rounded-btn:visited {\r
+        color: rgba(255, 255, 255, 1);\r
+        border: 2px solid rgba(255, 255, 255, 1);\r
+        outline: none;\r
+    }\r
+\r
+    h1 {\r
+        font-weight: 300;\r
+        margin-top: 20px;\r
+        margin-bottom: 10px;\r
+        font-size: 36px;\r
+        small {\r
+            color: rgba(255, 255, 255, 0.7);\r
+        }\r
+    }\r
+\r
+    .form-group {\r
+        padding: 8px 0;\r
+        input::-webkit-input-placeholder {\r
+            color: rgba(255, 255, 255, 0.6) !important;\r
+        }\r
+\r
+        input:-moz-placeholder {\r
+            /* Firefox 18- */\r
+            color: rgba(255, 255, 255, 0.6) !important;\r
+        }\r
+\r
+        input::-moz-placeholder {\r
+            /* Firefox 19+ */\r
+            color: rgba(255, 255, 255, 0.6) !important;\r
+        }\r
+\r
+        input:-ms-input-placeholder {\r
+            color: rgba(255, 255, 255, 0.6) !important;\r
+        }\r
+    }\r
+    .form-content {\r
+        padding: 30px 0;\r
+    }\r
+    .user-avatar {\r
+        -webkit-border-radius: 50%;\r
+        border-radius: 50%;\r
+        border: 2px solid #fff;\r
+    }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/login/login.component.spec.ts b/otf-frontend/client/src/app/login/login.component.spec.ts
new file mode 100644 (file)
index 0000000..2eb7b85
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'\r
+import { RouterTestingModule } from '@angular/router/testing'\r
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations'\r
+\r
+import { LoginComponent } from './login.component'\r
+import { LoginModule } from './login.module'\r
+\r
+describe('LoginComponent', () => {\r
+  let component: LoginComponent;\r
+  let fixture: ComponentFixture<LoginComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      imports: [\r
+        LoginModule,\r
+        RouterTestingModule,\r
+        BrowserAnimationsModule,\r
+      ],\r
+    })\r
+    .compileComponents()\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(LoginComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges()\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy()\r
+  })\r
+});\r
diff --git a/otf-frontend/client/src/app/login/login.component.ts b/otf-frontend/client/src/app/login/login.component.ts
new file mode 100644 (file)
index 0000000..3a17aad
--- /dev/null
@@ -0,0 +1,102 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { Router, ActivatedRoute } from '@angular/router';\r
+import { routerTransition } from '../router.animations';\r
+import { HttpClient } from '@angular/common/http';\r
+import { AppGlobals } from '../app.global';\r
+import { UserService } from '../shared/services/user.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { AuthService } from 'app/shared/services/auth.service';\r
+import { AlertModalComponent } from '../shared/modules/alert-modal/alert-modal.component';\r
+import { MatDialog } from '@angular/material';\r
+\r
+\r
+\r
+@Component({\r
+    selector: 'app-login',\r
+    templateUrl: './login.component.html',\r
+    providers: [],\r
+    styleUrls: ['./login.component.scss'],\r
+    animations: [routerTransition()]\r
+})\r
+export class LoginComponent implements OnInit {\r
+    public User;\r
+    public authResult;\r
+    public returnUrl;\r
+    public loginFailed = false;\r
+\r
+    constructor(public router: Router, \r
+        private route: ActivatedRoute,\r
+        private http: HttpClient,\r
+        private  _global: AppGlobals,\r
+        private cookie: CookieService,\r
+        private dialog: MatDialog,\r
+        private auth: AuthService\r
+    ) {}\r
+\r
+    ngOnInit() {\r
+        this.User={};\r
+        this.User.email = "";\r
+        this.User.password = "";\r
+        this.authResult={};\r
+\r
+        this.auth.logout();\r
+\r
+        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';\r
+    }\r
+\r
+    onLoggedin() {\r
+        //alert("User email: " + this.User.email + " User password: " + this.User.password);\r
+        this.auth.login(this.User) //need to use /authorization\r
+            .subscribe(\r
+                (authResult) => {\r
+                    if(this.cookie.check('access_token')){\r
+                        this.router.navigate([this.returnUrl]);\r
+                    }else {\r
+                        if (authResult['user'] && !authResult['user']['enabled']) {\r
+                            this.dialog.open(AlertModalComponent, {\r
+                                width: '450px',\r
+                                data: {\r
+                                    type: 'ok',\r
+                                    message: "Your account is not yet enabled. Please wait for approval."\r
+                                }\r
+                            });\r
+                        }\r
+                        else {\r
+                            this.dialog.open(AlertModalComponent, {\r
+                                width: '450px',\r
+                                data: {\r
+                                    type: 'alert',\r
+                                    message: "Something went wrong... Please Refresh and try again."\r
+                                }\r
+                            });\r
+                        }\r
+                    }\r
+                },\r
+                (error) => {\r
+                    this.loginFailed = true;\r
+                    this.dialog.open(AlertModalComponent, {\r
+                        width: '450px',\r
+                        data: {\r
+                            type: 'alert',\r
+                            message: error + " Please try again"\r
+                        }\r
+                    });\r
+                });\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/login/login.module.spec.ts b/otf-frontend/client/src/app/login/login.module.spec.ts
new file mode 100644 (file)
index 0000000..c4b2ce2
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { LoginModule } from './login.module';\r
+\r
+describe('LoginModule', () => {\r
+    let loginModule: LoginModule;\r
+\r
+    beforeEach(() => {\r
+        loginModule = new LoginModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(loginModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/login/login.module.ts b/otf-frontend/client/src/app/login/login.module.ts
new file mode 100644 (file)
index 0000000..5b72f0c
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { FormsModule } from '@angular/forms';\r
+import { LoginRoutingModule } from './login-routing.module';\r
+import { LoginComponent } from './login.component';\r
+import { AlertModalModule } from '../shared/modules/alert-modal/alert-modal.module';\r
+import { MatDialogModule } from '@angular/material';\r
+\r
+@NgModule({\r
+    imports: [CommonModule, LoginRoutingModule, FormsModule, AlertModalModule, MatDialogModule],\r
+    declarations: [LoginComponent]\r
+})\r
+export class LoginModule {}\r
diff --git a/otf-frontend/client/src/app/not-found/not-found-routing.module.ts b/otf-frontend/client/src/app/not-found/not-found-routing.module.ts
new file mode 100644 (file)
index 0000000..f7efefd
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { NotFoundComponent } from './not-found.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '', component: NotFoundComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class NotFoundRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/not-found/not-found.component.html b/otf-frontend/client/src/app/not-found/not-found.component.html
new file mode 100644 (file)
index 0000000..6a98bbd
--- /dev/null
@@ -0,0 +1,35 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<link href='https://fonts.googleapis.com/css?family=Anton|Passion+One|PT+Sans+Caption' rel='stylesheet' type='text/css'>\r
+<body>\r
+\r
+        <!-- Error Page -->\r
+            <div class="error">\r
+                <div class="container-floud">\r
+                    <div class="col-xs-12 ground-color text-center">\r
+                        <div class="container-error-404">\r
+                            <div class="clip"><div class="shadow"><span class="digit thirdDigit"></span></div></div>\r
+                            <div class="clip"><div class="shadow"><span class="digit secondDigit"></span></div></div>\r
+                            <div class="clip"><div class="shadow"><span class="digit firstDigit"></span></div></div>\r
+                            <div class="msg">OH!<span class="triangle"></span></div>\r
+                        </div>\r
+                        <h2 class="h1">Sorry! Page not found</h2>\r
+                    </div>\r
+                </div>\r
+            </div>\r
+        <!-- Error Page -->\r
+</body>
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/not-found/not-found.component.pug b/otf-frontend/client/src/app/not-found/not-found.component.pug
new file mode 100644 (file)
index 0000000..1c72049
--- /dev/null
@@ -0,0 +1,20 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.col-md-12.text-center\r
+    img(src="assets/images/404image.png", width="400px")\r
+    h2 Page Not Found\r
+    p The page you were trying to access could not be found!
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/not-found/not-found.component.scss b/otf-frontend/client/src/app/not-found/not-found.component.scss
new file mode 100644 (file)
index 0000000..26188f7
--- /dev/null
@@ -0,0 +1,241 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+*\r
+{\r
+  font-family: 'PT Sans Caption', sans-serif, 'arial', 'Times New Roman';\r
+}\r
+/* Error Page */\r
+    .error .clip .shadow\r
+    {\r
+        height: 180px;  /*Contrall*/\r
+    }\r
+    .error .clip:nth-of-type(2) .shadow\r
+    {\r
+        width: 130px;   /*Contrall play with javascript*/ \r
+    }\r
+    .error .clip:nth-of-type(1) .shadow, .error .clip:nth-of-type(3) .shadow\r
+    {\r
+        width: 250px; /*Contrall*/\r
+    }\r
+    .error .digit\r
+    {\r
+        width: 150px;   /*Contrall*/\r
+        height: 150px;  /*Contrall*/\r
+        line-height: 150px; /*Contrall*/\r
+        font-size: 120px;\r
+        font-weight: bold;\r
+    }\r
+    .error h2   /*Contrall*/\r
+    {\r
+        font-size: 32px;\r
+    }\r
+    .error .msg /*Contrall*/\r
+    {\r
+        top: -190px;\r
+        left: 30%;\r
+        width: 80px;\r
+        height: 80px;\r
+        line-height: 80px;\r
+        font-size: 32px;\r
+    }\r
+    .error span.triangle    /*Contrall*/\r
+    {\r
+        top: 70%;\r
+        right: 0%;\r
+        border-left: 20px solid #535353;\r
+        border-top: 15px solid transparent;\r
+        border-bottom: 15px solid transparent;\r
+    }\r
+\r
+\r
+    .error .container-error-404\r
+    {\r
+      margin-top: 10%;\r
+        position: relative;\r
+        height: 250px;\r
+        padding-top: 40px;\r
+    }\r
+    .error .container-error-404 .clip\r
+    {\r
+        display: inline-block;\r
+        transform: skew(-45deg);\r
+    }\r
+    .error .clip .shadow\r
+    {\r
+        \r
+        overflow: hidden;\r
+    }\r
+    .error .clip:nth-of-type(2) .shadow\r
+    {\r
+        overflow: hidden;\r
+        position: relative;\r
+        box-shadow: inset 20px 0px 20px -15px rgba(150, 150, 150,0.8), 20px 0px 20px -15px rgba(150, 150, 150,0.8);\r
+    }\r
+    \r
+    .error .clip:nth-of-type(3) .shadow:after, .error .clip:nth-of-type(1) .shadow:after\r
+    {\r
+        content: "";\r
+        position: absolute;\r
+        right: -8px;\r
+        bottom: 0px;\r
+        z-index: 9999;\r
+        height: 100%;\r
+        width: 10px;\r
+        background: linear-gradient(90deg, transparent, rgba(173,173,173, 0.8), transparent);\r
+        border-radius: 50%;\r
+    }\r
+    .error .clip:nth-of-type(3) .shadow:after\r
+    {\r
+        left: -8px;\r
+    }\r
+    .error .digit\r
+    {\r
+        position: relative;\r
+        top: 8%;\r
+        color: white;\r
+        background: #07B3F9;\r
+        border-radius: 50%;\r
+        display: inline-block;\r
+        transform: skew(45deg);\r
+    }\r
+    .error .clip:nth-of-type(2) .digit\r
+    {\r
+        left: -10%;\r
+    }\r
+    .error .clip:nth-of-type(1) .digit\r
+    {\r
+        right: -20%;\r
+    }.error .clip:nth-of-type(3) .digit\r
+    {\r
+        left: -20%;\r
+    }    \r
+    .error h2\r
+    {\r
+        color: #A2A2A2;\r
+        font-weight: bold;\r
+        padding-bottom: 20px;\r
+    }\r
+    .error .msg\r
+    {\r
+        position: relative;\r
+        z-index: 9999;\r
+        display: block;\r
+        background: #535353;\r
+        color: #A2A2A2;\r
+        border-radius: 50%;\r
+        font-style: italic;\r
+    }\r
+    .error .triangle\r
+    {\r
+        position: absolute;\r
+        z-index: 999;\r
+        transform: rotate(45deg);\r
+        content: "";\r
+        width: 0; \r
+        height: 0; \r
+    }\r
+\r
+/* Error Page */\r
+@media(max-width: 767px)\r
+{\r
+    /* Error Page */\r
+            .error .clip .shadow\r
+            {\r
+                height: 100px;  /*Contrall*/\r
+            }\r
+            .error .clip:nth-of-type(2) .shadow\r
+            {\r
+                width: 80px;   /*Contrall play with javascript*/ \r
+            }\r
+            .error .clip:nth-of-type(1) .shadow, .error .clip:nth-of-type(3) .shadow\r
+            {\r
+                width: 100px; /*Contrall*/\r
+            }\r
+            .error .digit\r
+            {\r
+                width: 80px;   /*Contrall*/\r
+                height: 80px;  /*Contrall*/\r
+                line-height: 80px; /*Contrall*/\r
+                font-size: 52px;\r
+            }\r
+            .error h2   /*Contrall*/\r
+            {\r
+                font-size: 24px;\r
+            }\r
+            .error .msg /*Contrall*/\r
+            {\r
+                top: -110px;\r
+                left: 15%;\r
+                width: 40px;\r
+                height: 40px;\r
+                line-height: 40px;\r
+                font-size: 18px;\r
+            }\r
+            .error span.triangle    /*Contrall*/\r
+            {\r
+                top: 70%;\r
+                right: -3%;\r
+                border-left: 10px solid #535353;\r
+                border-top: 8px solid transparent;\r
+                border-bottom: 8px solid transparent;\r
+            }\r
+.error .container-error-404\r
+  {\r
+    height: 150px;\r
+  }\r
+        /* Error Page */\r
+}\r
+\r
+/*--------------------------------------------Framework --------------------------------*/\r
+\r
+.overlay { position: relative; z-index: 20; } /*done*/\r
+    .ground-color { background: white; }  /*done*/\r
+    .item-bg-color { background: #EAEAEA } /*done*/\r
+    \r
+    /* Padding Section*/\r
+        .padding-top { padding-top: 10px; } /*done*/\r
+        .padding-bottom { padding-bottom: 10px; }   /*done*/\r
+        .padding-vertical { padding-top: 10px; padding-bottom: 10px; }\r
+        .padding-horizontal { padding-left: 10px; padding-right: 10px; }\r
+        .padding-all { padding: 10px; }   /*done*/\r
+\r
+        .no-padding-left { padding-left: 0px; }    /*done*/\r
+        .no-padding-right { padding-right: 0px; }   /*done*/\r
+        .no-vertical-padding { padding-top: 0px; padding-bottom: 0px; }\r
+        .no-horizontal-padding { padding-left: 0px; padding-right: 0px; }\r
+        .no-padding { padding: 0px; }   /*done*/\r
+    /* Padding Section*/\r
+\r
+    /* Margin section */\r
+        .margin-top { margin-top: 10px; }   /*done*/\r
+        .margin-bottom { margin-bottom: 10px; } /*done*/\r
+        .margin-right { margin-right: 10px; } /*done*/\r
+        .margin-left { margin-left: 10px; } /*done*/\r
+        .margin-horizontal { margin-left: 10px; margin-right: 10px; } /*done*/\r
+        .margin-vertical { margin-top: 10px; margin-bottom: 10px; } /*done*/\r
+        .margin-all { margin: 10px; }   /*done*/\r
+        .no-margin { margin: 0px; }   /*done*/\r
+\r
+        .no-vertical-margin { margin-top: 0px; margin-bottom: 0px; }\r
+        .no-horizontal-margin { margin-left: 0px; margin-right: 0px; }\r
+\r
+        .inside-col-shrink { margin: 0px 20px; }    /*done - For the inside sections that has also Title section*/ \r
+    /* Margin section */\r
+\r
+    hr\r
+    { margin: 0px; padding: 0px; border-top: 1px dashed #999; }\r
+/*--------------------------------------------FrameWork------------------------*/
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/not-found/not-found.component.spec.ts b/otf-frontend/client/src/app/not-found/not-found.component.spec.ts
new file mode 100644 (file)
index 0000000..ba9de87
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { NotFoundComponent } from './not-found.component';\r
+\r
+describe('NotFoundComponent', () => {\r
+  let component: NotFoundComponent;\r
+  let fixture: ComponentFixture<NotFoundComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ NotFoundComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(NotFoundComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/not-found/not-found.component.ts b/otf-frontend/client/src/app/not-found/not-found.component.ts
new file mode 100644 (file)
index 0000000..1aa6b9e
--- /dev/null
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';\r
+import * as $ from 'jquery';\r
+\r
+@Component({\r
+  selector: 'app-not-found',\r
+  templateUrl: './not-found.component.html',\r
+  styleUrls: ['./not-found.component.scss']\r
+})\r
+export class NotFoundComponent implements OnInit {\r
+\r
+  constructor() { }\r
+\r
+  ngOnInit() {\r
+    var loop1, loop2, loop3, time = 30, i = 0, number, selector3 = $('.thirdDigit'), selector2 = $('.secondDigit'),\r
+      selector1 = $('.firstDigit');\r
+    loop3 = setInterval(() => {\r
+      "use strict";\r
+      if (i > 40) {\r
+        clearInterval(loop3);\r
+        selector3.text(4);\r
+      } else {\r
+        selector3.text(this.randomNum());\r
+        i++;\r
+      }\r
+    }, time);\r
+    loop2 = setInterval(() => {\r
+      "use strict";\r
+      if (i > 80) {\r
+        clearInterval(loop2);\r
+        selector2.text(0);\r
+      } else {\r
+        selector2.text(this.randomNum());\r
+        i++;\r
+      }\r
+    }, time);\r
+    loop1 = setInterval( () => {\r
+      "use strict";\r
+      if (i > 100) {\r
+        clearInterval(loop1);\r
+        selector1.text(4);\r
+      } else {\r
+        selector1.text(this.randomNum());\r
+        i++;\r
+      }\r
+    }, time);\r
+  }\r
+\r
+  randomNum() {\r
+    "use strict";\r
+    return Math.floor(Math.random() * 9) + 1;\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/not-found/not-found.module.spec.ts b/otf-frontend/client/src/app/not-found/not-found.module.spec.ts
new file mode 100644 (file)
index 0000000..a24eea2
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NotFoundModule } from './not-found.module';\r
+\r
+describe('NotFoundModule', () => {\r
+  let notFoundModule: NotFoundModule;\r
+\r
+  beforeEach(() => {\r
+    notFoundModule = new NotFoundModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(notFoundModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/not-found/not-found.module.ts b/otf-frontend/client/src/app/not-found/not-found.module.ts
new file mode 100644 (file)
index 0000000..2bcb72f
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { NotFoundRoutingModule } from './not-found-routing.module';\r
+import { NotFoundComponent } from './not-found.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    NotFoundRoutingModule\r
+  ],\r
+  declarations: [NotFoundComponent]\r
+})\r
+export class NotFoundModule { }\r
diff --git a/otf-frontend/client/src/app/router.animations.ts b/otf-frontend/client/src/app/router.animations.ts
new file mode 100644 (file)
index 0000000..30cf59f
--- /dev/null
@@ -0,0 +1,178 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { animate, group, query, state, style, transition, trigger } from '@angular/animations';\r
+\r
+export function routerTransition() {\r
+    return slideToTop();\r
+}\r
+\r
+export function routerLeftTransition() {\r
+    return slideToLeft();\r
+}\r
+\r
+export function slideToRight() {\r
+    return trigger('routerTransition', [\r
+        state('void', style({})),\r
+        state('*', style({})),\r
+        transition(':enter', [\r
+            style({ transform: 'translateX(-100%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateX(0%)' }))\r
+        ]),\r
+        transition(':leave', [\r
+            style({ transform: 'translateX(0%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateX(100%)' }))\r
+        ])\r
+    ]);\r
+}\r
+\r
+export function slideToLeft() {\r
+    return trigger('routerTransition', [\r
+        state('void', style({})),\r
+        state('*', style({})),\r
+        transition(':enter', [\r
+            style({ transform: 'translateX(100%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateX(0%)' }))\r
+        ]),\r
+        transition(':leave', [\r
+            style({ transform: 'translateX(0%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateX(-100%)' }))\r
+        ])\r
+    ]);\r
+}\r
+\r
+export function slideToBottom() {\r
+    return trigger('routerTransition', [\r
+        state('void', style({})),\r
+        state('*', style({})),\r
+        transition(':enter', [\r
+            style({ transform: 'translateY(-100%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateY(0%)' }))\r
+        ]),\r
+        transition(':leave', [\r
+            style({ transform: 'translateY(0%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateY(100%)' }))\r
+        ])\r
+    ]);\r
+}\r
+\r
+export function slideToTop() {\r
+    return trigger('routerTransition', [\r
+        state('void', style({})),\r
+        state('*', style({})),\r
+        transition(':enter', [\r
+            style({ transform: 'translateY(100%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateY(0%)' }))\r
+        ]),\r
+        transition(':leave', [\r
+            style({ transform: 'translateY(0%)' }),\r
+            animate('0.5s ease-in-out', style({ transform: 'translateY(-100%)' }))\r
+        ])\r
+    ]);\r
+}\r
+\r
+  \r
+export function routerTransitionCustom() {\r
+    alert("");\r
+    return trigger('routerAnimation', [\r
+        state('void', style({})),\r
+        state('*', style({})),\r
+      // LEFT TO RIGHT AKA RESET\r
+      transition('* => 0', [\r
+    // Initial state of new route\r
+    query(':enter',\r
+        style({\r
+        position: 'fixed',\r
+        width: '100%',\r
+        transform: 'translateX(-100%)'\r
+        }), { optional: true }),\r
+    // move page off screen right on leave\r
+    query(':leave',\r
+        animate('500ms ease',\r
+        style({\r
+            position: 'fixed',\r
+            width: '100%',\r
+            transform: 'translateX(100%)',\r
+        })\r
+        ), { optional: true }),\r
+    // move page in screen from left to right\r
+    query(':enter',\r
+        animate('500ms ease',\r
+        style({\r
+            opacity: 1,\r
+            transform: 'translateX(0%)'\r
+        })\r
+        ), { optional: true }),\r
+    ]),\r
+    // LEFT TO RIGHT AKA PREVIOUS\r
+    transition('* => 1', [\r
+    // Initial state of new route\r
+    query(':enter',\r
+        style({\r
+        position: 'fixed',\r
+        width: '100%',\r
+        transform: 'translateX(-100%)'\r
+        }), { optional: true }),\r
+    // move page off screen right on leave\r
+    query(':leave',\r
+        animate('500ms ease',\r
+        style({\r
+            position: 'fixed',\r
+            width: '100%',\r
+            transform: 'translateX(100%)',\r
+        })\r
+        ), { optional: true }),\r
+    // move page in screen from left to right\r
+    query(':enter',\r
+        animate('500ms ease',\r
+        style({\r
+            opacity: 1,\r
+            transform: 'translateX(0%)'\r
+        })\r
+        ), { optional: true }),\r
+    ]),\r
+    // RIGHT TO LEFT AKA NEXT\r
+    transition('* => 2', [\r
+    // Initial state of new route\r
+    query(':enter',\r
+        style({\r
+        position: 'fixed',\r
+        width: '100%',\r
+        transform: 'translateX(100%)'\r
+        }), { optional: true }),\r
+    // move page off screen right on leave\r
+    query(':leave',\r
+        animate('500ms ease',\r
+        style({\r
+            position: 'fixed',\r
+            width: '100%',\r
+            transform: 'translateX(-100%)',\r
+        })\r
+        ), { optional: true }),\r
+    // move page in screen from left to right\r
+    query(':enter',\r
+        animate('500ms ease',\r
+        style({\r
+            opacity: 1,\r
+            transform: 'translateX(0%)'\r
+        })\r
+        ), { optional: true }),\r
+    ])\r
+    \r
+    ]);\r
+   \r
+}\r
+  
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/server-error/server-error-routing.module.ts b/otf-frontend/client/src/app/server-error/server-error-routing.module.ts
new file mode 100644 (file)
index 0000000..90615dc
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { ServerErrorComponent } from './server-error.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '', component: ServerErrorComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class ServerErrorRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/server-error/server-error.component.html b/otf-frontend/client/src/app/server-error/server-error.component.html
new file mode 100644 (file)
index 0000000..40b6cc4
--- /dev/null
@@ -0,0 +1,19 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<p>\r
+  server-error works!\r
+</p>\r
diff --git a/otf-frontend/client/src/app/server-error/server-error.component.scss b/otf-frontend/client/src/app/server-error/server-error.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/server-error/server-error.component.spec.ts b/otf-frontend/client/src/app/server-error/server-error.component.spec.ts
new file mode 100644 (file)
index 0000000..a0a97b8
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ServerErrorComponent } from './server-error.component';\r
+\r
+describe('ServerErrorComponent', () => {\r
+  let component: ServerErrorComponent;\r
+  let fixture: ComponentFixture<ServerErrorComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ServerErrorComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ServerErrorComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/server-error/server-error.component.ts b/otf-frontend/client/src/app/server-error/server-error.component.ts
new file mode 100644 (file)
index 0000000..31b4fb6
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+\r
+@Component({\r
+  selector: 'app-server-error',\r
+  templateUrl: './server-error.component.html',\r
+  styleUrls: ['./server-error.component.scss']\r
+})\r
+export class ServerErrorComponent implements OnInit {\r
+\r
+  constructor() { }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/server-error/server-error.module.spec.ts b/otf-frontend/client/src/app/server-error/server-error.module.spec.ts
new file mode 100644 (file)
index 0000000..c5decc5
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ServerErrorModule } from './server-error.module';\r
+\r
+describe('ServerErrorModule', () => {\r
+  let serverErrorModule: ServerErrorModule;\r
+\r
+  beforeEach(() => {\r
+    serverErrorModule = new ServerErrorModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(serverErrorModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/server-error/server-error.module.ts b/otf-frontend/client/src/app/server-error/server-error.module.ts
new file mode 100644 (file)
index 0000000..835a80b
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+import { ServerErrorRoutingModule } from './server-error-routing.module';\r
+import { ServerErrorComponent } from './server-error.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    ServerErrorRoutingModule\r
+  ],\r
+  declarations: [ServerErrorComponent]\r
+})\r
+export class ServerErrorModule { }\r
diff --git a/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.pug b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.pug
new file mode 100644 (file)
index 0000000..548ac7c
--- /dev/null
@@ -0,0 +1,29 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+mat-menu(#childMenu='matMenu', [overlapTrigger]='false')\r
+  span(*ngFor='let child of items')\r
+    // Handle branch node menu items\r
+    span(*ngIf='child.children && child.children.length > 0')\r
+      button(mat-menu-item='', color='primary', (click)="sendSelected(child)", [matMenuTriggerFor]='menu.childMenu')\r
+        mat-icon(*ngIf="child.iconName") {{child.iconName}}\r
+        span {{child.displayName}}\r
+      app-menu-item(#menu='', [items]='child.children', (dataEvent)="receiveSelected($event)")\r
+    // Handle leaf node menu items\r
+    span(*ngIf='!child.children || child.children.length === 0')\r
+      button(mat-menu-item='', (click)="sendSelected(child)")\r
+        mat-icon(*ngIf="child.iconName") {{child.iconName}}\r
+        span {{child.displayName}}\r
diff --git a/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.scss b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.spec.ts b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.spec.ts
new file mode 100644 (file)
index 0000000..73b7e97
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { MenuItemComponent } from './menu-item.component';\r
+\r
+describe('MenuItemComponent', () => {\r
+  let component: MenuItemComponent;\r
+  let fixture: ComponentFixture<MenuItemComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ MenuItemComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(MenuItemComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.ts b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.ts
new file mode 100644 (file)
index 0000000..426ca43
--- /dev/null
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, Output, EventEmitter } from '@angular/core';\r
+import { Router } from '@angular/router';\r
+\r
+export interface NavItem {\r
+  displayName: string;\r
+  disabled?: boolean;\r
+  iconName?: string;\r
+  route?: string;\r
+  click?: any;\r
+  children?: NavItem[];\r
+}\r
+\r
+@Component({\r
+  selector: 'app-menu-item',\r
+  templateUrl: './menu-item.component.pug',\r
+  styleUrls: ['./menu-item.component.scss']\r
+})\r
+\r
+export class MenuItemComponent implements OnInit {\r
+\r
+  @Input() items: NavItem[];\r
+  @ViewChild('childMenu') public childMenu;\r
+  @Output() dataEvent = new EventEmitter<any>();\r
+\r
+  constructor(public router: Router) { }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+  receiveSelected($event){\r
+    this.sendSelected($event);\r
+  }\r
+\r
+  sendSelected(data){\r
+    this.dataEvent.emit(data)\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/factories/bpmn-factory.service.spec.ts b/otf-frontend/client/src/app/shared/factories/bpmn-factory.service.spec.ts
new file mode 100644 (file)
index 0000000..1068c95
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { BpmnFactoryService } from './bpmn-factory.service';\r
+\r
+describe('BpmnFactoryService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: BpmnFactoryService = TestBed.get(BpmnFactoryService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/factories/bpmn-factory.service.ts b/otf-frontend/client/src/app/shared/factories/bpmn-factory.service.ts
new file mode 100644 (file)
index 0000000..1a92854
--- /dev/null
@@ -0,0 +1,132 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { FileTransferService } from '../services/file-transfer.service';\r
+import { TestDefinitionService } from '../services/test-definition.service';\r
+import { Observable } from 'rxjs';\r
+import { Buffer } from 'buffer';\r
+import { BpmnOptions, Bpmn } from '../models/bpmn.model';\r
+\r
+interface BpmnFactoryOptions extends BpmnOptions {\r
+  fileId?: String,\r
+  testDefinitionId?: String,\r
+  version?: String,\r
+  xml?: String\r
+}\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class BpmnFactoryService {\r
+\r
+  constructor(\r
+    private filesTransfer: FileTransferService,\r
+    private testDefinition: TestDefinitionService\r
+  ) { }\r
+\r
+  public async setup(options: BpmnFactoryOptions): Promise<any> {\r
+    return new Promise(async (resolve, reject) => {\r
+      //check for required options\r
+      if (!options.mode) {\r
+        console.error('Bpmn options require: mode');\r
+        reject('Bpmn options require: mode')\r
+      }\r
+\r
+      let xml = await this.getXml(options);\r
+\r
+      let instance = new Bpmn(xml, {\r
+        mode: options.mode,\r
+        options: options.options\r
+      })\r
+\r
+      resolve(instance);\r
+    });\r
+\r
+  }\r
+\r
+  public async getXml(options): Promise<any> {\r
+    return new Promise(async (resolve, reject) => {\r
+      let xml;\r
+\r
+      //handle the way to retrieve bpmn xml\r
+      if (options.xml) {\r
+        xml = options.xml\r
+      } else if (options.fileId) {\r
+        xml = await this.loadFile(options.fileId);\r
+      } else if (options.testDefinitionId && options.version) {\r
+        let fileId = await this.getFileId(options.testDefinitionId, options.version);\r
+        xml = await this.loadFile(fileId);\r
+      } else if (options.testDefinitionId) {\r
+        let fileId = await this.getFileId(options.testDefinitionId);\r
+        xml = await this.loadFile(fileId);\r
+      } else {\r
+        console.warn('Either xml, fileId, testDefinitionId and version, or testDefinitionId is required to render the bpmn');\r
+      }\r
+\r
+      resolve(xml);\r
+\r
+    });\r
+\r
+  }\r
+\r
+  private getFileId(id, version?): Observable<Object> {\r
+    return new Observable(observer => {\r
+      this.testDefinition.get(id).subscribe(\r
+        data => {\r
+          if (data['bpmnInstances']) {\r
+            if (version) {\r
+              let index;\r
+              for (let i = 0; i < data['bpmnInstances'].length; i++) {\r
+                if (version == data['bpmnInstances'][i].version) {\r
+                  index = i;\r
+                  break;\r
+                }\r
+              }\r
+              if (index) {\r
+                observer.next(data['bpmnInstances'][index].bpmnFileId);\r
+              } else {\r
+                observer.error('No bpmn file');\r
+              }\r
+\r
+            } else {\r
+              if (data['bpmnInstances'][data['bpmnInstances'].length - 1].bpmnFileId) {\r
+                observer.next(data['bpmnInstances'][data['bpmnInstances'].length - 1].bpmnFileId);\r
+              } else {\r
+                observer.error('No bpmn file');\r
+              }\r
+            }\r
+          } else {\r
+            observer.error('No bpmn instances');\r
+          }\r
+        },\r
+        err => {\r
+          observer.error('No test definition found');\r
+        }\r
+      )\r
+    })\r
+  }\r
+\r
+  public loadFile(bpmnFileId) {\r
+    return new Promise((resolve, reject) => {\r
+      this.filesTransfer.get(bpmnFileId).subscribe(content => {\r
+        resolve(new Buffer(content as Buffer).toString());\r
+      }, err => {\r
+        reject(err);\r
+      });\r
+    });\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/guard/admin.guard.spec.ts b/otf-frontend/client/src/app/shared/guard/admin.guard.spec.ts
new file mode 100644 (file)
index 0000000..ec3d6ba
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, async, inject } from '@angular/core/testing';\r
+import { RouterTestingModule } from '@angular/router/testing';\r
+\r
+import { AdminGuard } from './admin.guard';\r
+\r
+describe('AdminGuard', () => {\r
+    beforeEach(() => {\r
+        TestBed.configureTestingModule({\r
+            imports: [ RouterTestingModule ],\r
+            providers: [AdminGuard]\r
+        });\r
+    });\r
+\r
+    it('should ...', inject([AdminGuard], (guard: AdminGuard) => {\r
+        expect(guard).toBeTruthy();\r
+    }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/guard/admin.guard.ts b/otf-frontend/client/src/app/shared/guard/admin.guard.ts
new file mode 100644 (file)
index 0000000..12e2cc9
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';\r
+import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http';\r
+import { AppGlobals } from 'app/app.global';\r
+import { UserService } from '../services/user.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+\r
+@Injectable()\r
+export class AdminGuard implements CanActivate {\r
+\r
+    constructor(private router: Router, private http: HttpClient, private cookie: CookieService) { }\r
+\r
+    async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {\r
+\r
+        if (this.cookie.get('access_token') && this.cookie.get('currentUser')) {\r
+            let currentUser = JSON.parse(this.cookie.get('currentUser'));\r
+            if(currentUser['permissions'].indexOf('admin') >= 0){\r
+                return true;\r
+            }\r
+            else{\r
+                this.router.navigate(['/dashboard'], { queryParams: { returnUrl: state.url }});\r
+                return false;\r
+            }\r
+        }\r
+        // not logged in so redirect to login page with the return url\r
+        this.router.navigate(['/dashboard'], { queryParams: { returnUrl: state.url }});\r
+        return false;\r
+\r
+\r
+\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/guard/auth.guard.spec.ts b/otf-frontend/client/src/app/shared/guard/auth.guard.spec.ts
new file mode 100644 (file)
index 0000000..a13902d
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, async, inject } from '@angular/core/testing';\r
+import { RouterTestingModule } from '@angular/router/testing';\r
+\r
+import { AuthGuard } from './auth.guard';\r
+\r
+describe('AuthGuard', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      imports: [ RouterTestingModule ],\r
+      providers: [AuthGuard]\r
+    });\r
+  });\r
+\r
+  it('should ...', inject([AuthGuard], (guard: AuthGuard) => {\r
+    expect(guard).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/guard/auth.guard.ts b/otf-frontend/client/src/app/shared/guard/auth.guard.ts
new file mode 100644 (file)
index 0000000..621bb0e
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';\r
+import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http';\r
+import { AppGlobals } from 'app/app.global';\r
+import { UserService } from '../services/user.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+\r
+@Injectable()\r
+export class AuthGuard implements CanActivate {\r
+\r
+    constructor(private router: Router, private http: HttpClient, private cookie: CookieService) { }\r
+\r
+    async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {\r
+        if (this.cookie.check('access_token') && window.localStorage.getItem('access_token')) {\r
+            return true;\r
+        }\r
+\r
+        // not logged in so redirect to login page with the return url\r
+        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});\r
+        return false;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/guard/index.ts b/otf-frontend/client/src/app/shared/guard/index.ts
new file mode 100644 (file)
index 0000000..30a32cf
--- /dev/null
@@ -0,0 +1,18 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export * from './auth.guard';\r
+export * from './admin.guard';\r
diff --git a/otf-frontend/client/src/app/shared/index.ts b/otf-frontend/client/src/app/shared/index.ts
new file mode 100644 (file)
index 0000000..edc253b
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export * from './modules';\r
+export * from './pipes/shared-pipes.module';\r
+export * from './guard';\r
diff --git a/otf-frontend/client/src/app/shared/models/base-model.model.ts b/otf-frontend/client/src/app/shared/models/base-model.model.ts
new file mode 100644 (file)
index 0000000..a89a591
--- /dev/null
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export interface BaseModel {\r
+    _id: String;\r
+    createdAt: String;\r
+    createdBy: String;\r
+    updatedAt: String;\r
+    updatedBy: String;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/models/bpmn.model.ts b/otf-frontend/client/src/app/shared/models/bpmn.model.ts
new file mode 100644 (file)
index 0000000..ab274f8
--- /dev/null
@@ -0,0 +1,159 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import Modeler from 'bpmn-js/lib/Modeler';\r
+import Viewer from 'bpmn-js/lib/NavigatedViewer';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { Observable } from 'rxjs';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+\r
+import { saveAs } from 'file-saver';\r
+//import { parseString } from 'xml2js';\r
+import { HostListener } from '@angular/core';\r
+\r
+export interface BpmnOptions {\r
+    mode: 'viewer' | 'modeler',\r
+    options: {\r
+        container: any\r
+    }\r
+}\r
+\r
+export class Bpmn {\r
+\r
+    protected model: any;\r
+    protected bpmnXml: String;\r
+    private options: BpmnOptions;\r
+\r
+    constructor(bpmnXml: String, options: BpmnOptions) {\r
+        //check for required options\r
+        if (!options.mode) {\r
+            console.error('Bpmn options require: mode');\r
+        }\r
+\r
+        this.bpmnXml = bpmnXml;\r
+        this.options = options;\r
+\r
+        //setup model\r
+        this.setModel();\r
+\r
+        //render diagram\r
+        this.renderDiagram();\r
+    }\r
+\r
+    // Getters\r
+\r
+    public getModel() {\r
+        return this.model;\r
+    }\r
+\r
+    public async getBpmnXml() {\r
+        return new Promise((resolve, reject) => {\r
+            this.model.saveXML({ format: true }, function (err, xml) {\r
+                if(err){\r
+                    reject(err);\r
+                }\r
+                resolve(xml);\r
+            })\r
+        });\r
+    }\r
+\r
+    // Setters\r
+\r
+    private setModel(options?) {\r
+\r
+        if (this.model) {\r
+            return -1;\r
+        }\r
+\r
+        let op = this.options.options;\r
+\r
+        if (options) {\r
+            op = options;\r
+        }\r
+\r
+        if (!op) {\r
+            console.error('Options for the viewer/modeler must be provided');\r
+            return -1;\r
+        }\r
+\r
+        //handle the mode (viewer or modeler)\r
+        switch (this.options.mode.toLowerCase()) {\r
+            case 'viewer':\r
+                this.model = new Viewer(op);\r
+                break;\r
+\r
+            case 'modeler':\r
+                this.model = new Modeler(op);\r
+                break;\r
+\r
+            default:\r
+                console.error('Mode must either be "viewer" or "modeler"');\r
+                return;\r
+        }\r
+\r
+    }\r
+\r
+    public async setBpmnXml(xml) {\r
+        this.bpmnXml = xml;\r
+        await this.renderDiagram();\r
+    }\r
+\r
+    // Methods\r
+\r
+    public async renderDiagram() {\r
+        return new Promise((resolve, reject) => {\r
+            if (this.bpmnXml) {\r
+                this.model.importXML(this.bpmnXml, (err) => {\r
+                    if (!err) {\r
+                        this.model.get('canvas').zoom('fit-viewport');\r
+                        resolve(true)\r
+                    } else {\r
+                        console.error(err);\r
+                        resolve(false);\r
+                    }\r
+                });\r
+            }\r
+        })\r
+    }\r
+\r
+    public resize() {\r
+        this.model.get('canvas').zoom('fit-viewport');\r
+    }\r
+\r
+    public download(saveName?) {\r
+\r
+        this.model.saveXML({ format: true }, function (err, xml) {\r
+            if (!saveName) {\r
+                let parser = new DOMParser();\r
+                let xmlDoc = parser.parseFromString(xml.toString(), "text/xml");\r
+\r
+                let id = xmlDoc.getElementsByTagName("bpmn:process")[0].attributes.getNamedItem("id").value;\r
+                \r
+                if (id) {\r
+                    saveName = id;\r
+                } else {\r
+                    saveName = 'workflow';\r
+                }\r
+            }\r
+\r
+            saveName += ".bpmn";\r
+\r
+            let blob = new Blob([xml], { type: "application/xml" });\r
+            saveAs(blob, saveName);\r
+        })\r
+    }\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/models/group.model.ts b/otf-frontend/client/src/app/shared/models/group.model.ts
new file mode 100644 (file)
index 0000000..86538fa
--- /dev/null
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { BaseModel } from "./base-model.model";\r
+\r
+export interface Group extends BaseModel{\r
+\r
+    groupName: String;\r
+    groupDescription: String;\r
+    parentGroupId: String;\r
+    ownerId: String;\r
+    mechanizedIds: Array<String>;\r
+\r
+}\r
+\r
+\r
+export class Groups implements Group {\r
+    groupName: String;\r
+    groupDescription: String;\r
+    parentGroupId: String;\r
+    ownerId: String;\r
+    mechanizedIds: String[];\r
+    _id: String;\r
+    createdAt: String;\r
+    createdBy: String;\r
+    updatedAt: String;\r
+    updatedBy: String;\r
+\r
+    static get modelName(){\r
+        return 'groups';\r
+    }\r
+\r
+    constructor(group){\r
+        this._id = group._id;\r
+        this.groupName = group.groupName;\r
+        this.parentGroupId = group.parentGroupId;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/models/test-definition.model.ts b/otf-frontend/client/src/app/shared/models/test-definition.model.ts
new file mode 100644 (file)
index 0000000..c996761
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { BaseModel } from "./base-model.model";\r
+\r
+export interface TestDefinition extends BaseModel {\r
+    \r
+    testName: String;\r
+    testDescription: String;\r
+    processDefinitionKey: String;\r
+    groupId: String;\r
+\r
+    bpmnInstances: Array<BpmnInstance>;\r
+\r
+    disabled: Boolean;\r
+\r
+}\r
+\r
+export interface BpmnInstance {\r
+    processDefinitionId: String;\r
+    deploymentId: String;\r
+    version: String;\r
+    bpmnFileId: String;\r
+    resourceFileId: String;\r
+    isDeployed: Boolean;\r
+\r
+    testHeads: Array<TestHeadRef>;\r
+    pflos: Array<Pflow>;\r
+\r
+    testDataTemplate: Object;\r
+\r
+    updatedBy: String;\r
+    createdBy: String;\r
+    createdAt: String;\r
+    updatedAt: String;\r
+}\r
+\r
+export interface TestHeadRef {\r
+    testHeadId: String;\r
+    bpmnVthTaskId: String;\r
+    label: String;\r
+}\r
+\r
+export interface Pflow {\r
+    bpmnPflowTaskId: String;\r
+    label: String;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/models/test-execution.model.ts b/otf-frontend/client/src/app/shared/models/test-execution.model.ts
new file mode 100644 (file)
index 0000000..9f410b5
--- /dev/null
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestDefinition } from "./test-definition.model";\r
+import { TestInstance } from "./test-instance.model";\r
+\r
+export interface TestExecution {\r
+\r
+    _id: String;\r
+    processInstanceId: String;\r
+    businessKey: String;\r
+    testResult: String;\r
+    testDetails: Object;\r
+    startTime: Date;\r
+    endTime: Date;\r
+    async: Boolean;\r
+    asyncTopic: String;\r
+    groupId: String;\r
+    executorId: String;\r
+    testHeadResults: Array<Object>;\r
+    testInstanceResults: Array<Object>;\r
+    historicEmail: String;\r
+    historicTestInstance: TestInstance;\r
+    historicTestDefinition: TestDefinition;\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/models/test-head.model.ts b/otf-frontend/client/src/app/shared/models/test-head.model.ts
new file mode 100644 (file)
index 0000000..ef28772
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { BaseModel } from "./base-model.model";\r
+\r
+export interface TestHead extends BaseModel {\r
+\r
+    testHeadName: String;\r
+    testHeadDescription: String;\r
+    testHeadType: String;\r
+    vthInputTemplate: Object;\r
+    vendor: String;\r
+    port: String;\r
+    hostname: String;\r
+    resourcePath: String;\r
+    groupId: String;\r
+    authorizationType: String,\r
+    authorizationCredential: String,\r
+    authorizationEnabled: Boolean,\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/models/test-instance.model.ts b/otf-frontend/client/src/app/shared/models/test-instance.model.ts
new file mode 100644 (file)
index 0000000..9bac1c0
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { BaseModel } from "./base-model.model";\r
+\r
+export interface TestInstance extends BaseModel {\r
+    \r
+    testInstanceName: String;\r
+    testInstanceDescription: String;\r
+    testDefinitionId: String;\r
+    useLatestDefinition: Boolean;\r
+    processDefinitionId: String;\r
+    testData: Object;\r
+    internalTestData: Object;\r
+    simulationMode: Boolean;\r
+    simulationVthInput: Object;\r
+    vthInput: Object;\r
+    pfloInput: Object;\r
+    disabled: Boolean;\r
+    maxExecutionTimeInMillis: Number;\r
+    groupId: String;\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/models/user.model.ts b/otf-frontend/client/src/app/shared/models/user.model.ts
new file mode 100644 (file)
index 0000000..9e75b95
--- /dev/null
@@ -0,0 +1,45 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { BaseModel } from "./base-model.model";\r
+\r
+export interface User extends BaseModel {\r
+\r
+    firstName: String;\r
+    lastName: String;\r
+    email: String;\r
+    permissions: Array<String>;\r
+    password: String;\r
+\r
+    groups: Array<GroupRef>;\r
+\r
+    favorites: Object;\r
+\r
+    enabled: Boolean;\r
+    isVerified: Boolean;\r
+    verifyToken: String;\r
+    verifyExpires: Date;\r
+    verifyChanges: Object;\r
+\r
+    resetToken: String;\r
+    resetExpires: DataCue;\r
+\r
+}\r
+\r
+interface GroupRef {\r
+    groupId: String;\r
+    permissions: Array<String>;\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.pug b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.pug
new file mode 100644 (file)
index 0000000..1db2cd3
--- /dev/null
@@ -0,0 +1,83 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(*ngIf = 'type == "warning"')\r
+    h2(mat-dialog-title)\r
+        i.fa.fa-warning\r
+        div Warning\r
+    mat-dialog-content\r
+        p(style="text-align: center") {{data.message}}\r
+    mat-dialog-actions\r
+        button(mat-raised-button, color="primary", aria-label='View', (click) = "okay()") OK\r
+\r
+div(*ngIf = 'type == "confirmation"')\r
+    h2(mat-dialog-title)\r
+        i.fa.fa-check\r
+        div Confirm\r
+    mat-dialog-content\r
+        p(style="text-align: center") {{data.message}}\r
+    mat-dialog-actions\r
+        button(mat-raised-button, color="primary", aria-label='Yes', (click) = "confirmed()") Yes\r
+        button(mat-raised-button, color="primary", aria-label='Cancel', (click) = "canceled()") Cancel\r
+\r
+div(*ngIf = 'type == "userAdmin"')\r
+    h2(mat-dialog-title)\r
+        i.fa.fa-question\r
+        div Choose\r
+    mat-dialog-content\r
+        p(style="text-align: center") {{data.message}}\r
+    mat-dialog-actions\r
+        button(mat-raised-button, color="primary", aria-label='Yes', (click) = "confirmed()") Admin\r
+        button(mat-raised-button, color="primary", aria-label='Cancel', (click) = "canceled()") User\r
+\r
+div(*ngIf = 'type == "alert"')\r
+    h2(mat-dialog-title)\r
+        i.fa.fa-times-circle-o\r
+        div Error\r
+    mat-dialog-content\r
+        p(style="text-align: center") {{data.message}}\r
+    mat-dialog-actions\r
+        button.bg-primary.mbtn.text-white.mr-1(mat-raised-button, aria-label='OK', (click) = "okay()") OK\r
+\r
+div(*ngIf = 'type == "ok"')\r
+    h2(mat-dialog-title)\r
+        i.fa.fa-check\r
+        div Alert\r
+    mat-dialog-content\r
+        p(*ngIf="html", [innerHtml]="html")\r
+        p(*ngIf="!html", style="text-align: center") {{data.message}}\r
+    mat-dialog-actions\r
+        button.bg-primary.mbtn.text-white.mr-1(mat-raised-button, aria-label='OK', (click) = "okay()") OK\r
+\r
+div(*ngIf = 'type == "info"')\r
+    h2(mat-dialog-title)\r
+        div Info\r
+    mat-dialog-content\r
+        p(*ngIf="html", [innerHtml]="html")\r
+        p(*ngIf="!html", style="text-align: center") {{data.message}}\r
+    mat-dialog-actions\r
+        button.bg-primary.mbtn.text-white.mr-1(mat-raised-button, aria-label='OK', (click) = "okay()") OK\r
+\r
+\r
+\r
+//<h1 mat-dialog-title>Add file</h1>\r
+    <mat-dialog-content>\r
+      Content goes here\r
+    </mat-dialog-content>\r
+    <mat-dialog-actions>\r
+      <button mat-button>Add</button>\r
+      <button mat-button>Cancel</button>\r
+    </mat-dialog-actions>\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.scss b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.scss
new file mode 100644 (file)
index 0000000..608c6cc
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.fa-warning{\r
+    color: #FFCC00;\r
+}\r
+.fa-times-circle-o{\r
+    color: red;\r
+}\r
+\r
+.fa-check {\r
+    color: green;\r
+}\r
+\r
+mat-dialog-actions .mat-raised-button {\r
+    margin: auto;\r
+}\r
+\r
+.mat-dialog-title {\r
+    text-align: center;\r
+}\r
+\r
+i {\r
+    font-size: 30px;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..01ad3d6
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { AlertModalComponent } from './alert-modal.component';\r
+\r
+describe('AlertModalComponent', () => {\r
+  let component: AlertModalComponent;\r
+  let fixture: ComponentFixture<AlertModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ AlertModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(AlertModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.ts b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.ts
new file mode 100644 (file)
index 0000000..6904ebf
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import {Component, Inject, OnInit} from '@angular/core';\r
+import {MAT_DIALOG_DATA, MatDialogRef,} from '@angular/material';\r
+\r
+@Component({\r
+    selector: 'app-alert-modal',\r
+    templateUrl: './alert-modal.component.pug',\r
+    styleUrls: ['./alert-modal.component.scss']\r
+})\r
+export class AlertModalComponent implements OnInit {\r
+    public data;\r
+    public type;\r
+    public html;\r
+\r
+    constructor(\r
+        public dialogRef: MatDialogRef<AlertModalComponent>,\r
+        @Inject(MAT_DIALOG_DATA) public input_data\r
+    ) {\r
+        this.data = this.input_data;\r
+        if (this.data.type.match(new RegExp('^warning$', 'i'))) {\r
+            this.type = 'warning';\r
+        } else if (this.data.type.match(new RegExp('^confirmation$', 'i'))) {\r
+            this.type = 'confirmation';\r
+        } else if (this.data.type.match(new RegExp('^alert$', 'i'))) {\r
+            this.type = 'alert';\r
+        } else if (this.data.type.match(new RegExp('^ok$', 'i'))) {\r
+            this.type = 'ok';\r
+        } else if (this.data.type.match(new RegExp('^userAdmin$', 'i'))) {\r
+            this.type = 'userAdmin';\r
+        } else {\r
+            this.type = 'info';\r
+        }\r
+    }\r
+\r
+    ngOnInit() {\r
+        if(this.data.html){\r
+            this.html = this.data.html;\r
+        }\r
+    }\r
+\r
+    okay() {\r
+        this.dialogRef.close();\r
+    }\r
+\r
+    confirmed() {\r
+        this.dialogRef.close(true);\r
+    }\r
+\r
+    canceled() {\r
+        this.dialogRef.close(false);\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..bfe72d1
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { AlertModalModule } from './alert-modal.module';\r
+\r
+describe('AlertModalModule', () => {\r
+  let alertModalModule: AlertModalModule;\r
+\r
+  beforeEach(() => {\r
+    alertModalModule = new AlertModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(alertModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.ts b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.ts
new file mode 100644 (file)
index 0000000..3fb4e6b
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { AlertModalComponent } from './alert-modal.component';\r
+import { MatDialogModule, MatButtonModule} from '@angular/material';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { FormsModule } from '@angular/forms';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    MatButtonModule,\r
+    MatDialogModule,\r
+    FilterPipeModule,\r
+    FormsModule\r
+  ],\r
+  declarations: [AlertModalComponent],\r
+  exports: [ AlertModalComponent],\r
+  entryComponents: [AlertModalComponent]\r
+})\r
+export class AlertModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.pug b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.pug
new file mode 100644 (file)
index 0000000..ea28f8f
--- /dev/null
@@ -0,0 +1,19 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.pull-left \r
+  mat-icon(style="color: green") check\r
+.pull-right {{ data.message }}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.scss b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.spec.ts b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.spec.ts
new file mode 100644 (file)
index 0000000..c17a50e
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { AlertSnackbarComponent } from './alert-snackbar.component';\r
+\r
+describe('AlertSnackbarComponent', () => {\r
+  let component: AlertSnackbarComponent;\r
+  let fixture: ComponentFixture<AlertSnackbarComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ AlertSnackbarComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(AlertSnackbarComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.ts b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.ts
new file mode 100644 (file)
index 0000000..dd0c1d0
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import { MAT_SNACK_BAR_DATA, } from '@angular/material';\r
+\r
+@Component({\r
+  selector: 'app-alert-snackbar',\r
+  templateUrl: './alert-snackbar.component.pug',\r
+  styleUrls: ['./alert-snackbar.component.scss']\r
+})\r
+export class AlertSnackbarComponent implements OnInit {\r
+\r
+  public data;\r
+\r
+  constructor(@Inject(MAT_SNACK_BAR_DATA) public input_data) { \r
+    this.data = input_data;\r
+  }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.spec.ts b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.spec.ts
new file mode 100644 (file)
index 0000000..d7eb3bc
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { AlertSnackbarModule } from './alert-snackbar.module';\r
+\r
+describe('AlertSnackbarModule', () => {\r
+  let alertSnackbarModule: AlertSnackbarModule;\r
+\r
+  beforeEach(() => {\r
+    alertSnackbarModule = new AlertSnackbarModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(alertSnackbarModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.ts b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.ts
new file mode 100644 (file)
index 0000000..5910877
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { AlertSnackbarComponent } from './alert-snackbar.component';\r
+import { MatSnackBarModule, MatIconModule} from '@angular/material';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    MatSnackBarModule,\r
+    MatIconModule\r
+  ],\r
+  declarations: [AlertSnackbarComponent],\r
+  entryComponents: [AlertSnackbarComponent]\r
+})\r
+export class AlertSnackbarModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.pug b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.pug
new file mode 100644 (file)
index 0000000..82c37a4
--- /dev/null
@@ -0,0 +1,37 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div([@routerTransition])\r
+  app-page-header([heading]="'Create Group'")\r
+  form.ml-2(style="width:100%")\r
+    .row\r
+      .col-md-6\r
+        .row\r
+          mat-form-field.mr-2\r
+            mat-select([(value)]="newGroup.parentGroupId", placeholder="Parent Group", required)\r
+              mat-option(value="None") None\r
+              mat-option(*ngFor="let group of groups", [value]="group._id") {{group.groupName}} \r
+              \r
+        .row\r
+          mat-form-field.mr-2(required)\r
+            input(matInput, placeholder="New Group Name", [(ngModel)]="newGroup.groupName", name="Group Name", required)\r
+        \r
+      .col-md-6\r
+        mat-form-field.mr-2\r
+          textarea(matInput, cdkTextareaAutosize, placeholder="Description", #autosize="cdkTextareaAutosize", cdkAutosizeMinRows="2", cdkAutosizeMaxRows="5", name="description", [(ngModel)]="newGroup.groupDescription")\r
+\r
+    button.pull-left(mat-raised-button, color="primary", (click)="createGroup()") Create\r
+    button.pull-right(mat-raised-button, color="warn", (click)="close()") Cancel\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.scss b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..f6b0153
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { CreateGroupModalComponent } from './create-group-modal.component';\r
+\r
+describe('CreateGroupModalComponent', () => {\r
+  let component: CreateGroupModalComponent;\r
+  let fixture: ComponentFixture<CreateGroupModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ CreateGroupModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(CreateGroupModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.ts b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.ts
new file mode 100644 (file)
index 0000000..2d0befc
--- /dev/null
@@ -0,0 +1,131 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import { MatDialogRef, MAT_DIALOG_DATA, MatSnackBar, MatDialog } from '@angular/material';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';\r
+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';\r
+\r
+\r
+@Component({\r
+  selector: 'app-create-group-modal',\r
+  templateUrl: './create-group-modal.component.pug',\r
+  styleUrls: ['./create-group-modal.component.scss']\r
+})\r
+export class CreateGroupModalComponent implements OnInit {\r
+\r
+  constructor(public dialogRef: MatDialogRef<CreateGroupModalComponent>, @Inject(MAT_DIALOG_DATA) public input_data, private userService: UserService, private groupService: GroupService, private cookieService: CookieService, private snack: MatSnackBar, private modal: MatDialog) { }\r
+\r
+  public groups;\r
+  public newGroup;\r
+  public user;\r
+\r
+  ngOnInit() {\r
+    this.newGroup = {};\r
+    this.user = {};\r
+    this.groups = [];\r
+    this.newGroup.groupName = '';\r
+    this.newGroup.parentGroupId = null;\r
+    this.user._id = this.userService.getId();\r
+    this.newGroup.ownerId = this.user["_id"];\r
+    //filter list of groups by the Admin permssion from the user\r
+    //Also add group onto active dropdown list when this dialog is closed\r
+    this.groupService.find({\r
+      $limit: -1\r
+      \r
+    }).subscribe((list) => {\r
+      //console.log(list);\r
+      for(let i in list){\r
+        //console.log(this.user._id + "     " + list[i]);\r
+        if(this.checkIsAdmin(list[i], this.user._id)){\r
+          this.groups.push(list[i]);\r
+        }\r
+      }\r
+      \r
+    });\r
+\r
+  }\r
+\r
+  checkIsAdmin(group, userId){\r
+    if(group.members){\r
+      let memberIndex =  group.members.findIndex(function(member){return member.userId.toString() == userId.toString()});\r
+      if(memberIndex >= 0){\r
+        if(group.members[memberIndex].roles.includes("admin")){\r
+          return true;\r
+        }\r
+      }\r
+    }\r
+    return false;\r
+  }\r
+\r
+  close(){\r
+    \r
+    this.dialogRef.close(null);\r
+  }\r
+\r
+  createGroup(){\r
+    \r
+    //console.log(this.newGroup);\r
+    if(this.newGroup.parentGroupId == "None"){\r
+      this.newGroup.parentGroupId = null;\r
+    }\r
+    \r
+    this.newGroup.roles = [{\r
+      roleName: "admin",\r
+      permissions: ["management", "write", "delete", "read", "execute"]\r
+    },\r
+    {\r
+      roleName: "user",\r
+      permissions: ["read"]\r
+    },\r
+    {\r
+      roleName: "developer",\r
+      permissions: ["write", "delete", "read", "execute"]\r
+    }];\r
+    this.newGroup.members = [{\r
+      userId: this.user._id,\r
+      roles: ["admin"]\r
+    }];\r
+    this.groupService.create(this.newGroup).subscribe(res => { \r
+      \r
+      let snackMessage = 'The group ' + this.newGroup.groupName + " has been created!";\r
+      this.snack.openFromComponent(AlertSnackbarComponent, {\r
+          duration: 1500,\r
+          data: {\r
+              message: snackMessage\r
+          }\r
+      });\r
+      if(res){\r
+        this.dialogRef.close(res)\r
+      }else{\r
+        this.close();\r
+      }\r
+    }, (error) => {\r
+        this.modal.open(AlertModalComponent, {\r
+          width: "250px",\r
+          data: {\r
+              type: "alert",\r
+              message: error\r
+          }\r
+        });\r
+    }); \r
+    \r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..91ecd63
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { CreateGroupModalModule } from './create-group-modal.module';\r
+\r
+describe('CreateGroupModalModule', () => {\r
+  let createGroupModalModule: CreateGroupModalModule;\r
+\r
+  beforeEach(() => {\r
+    createGroupModalModule = new CreateGroupModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(createGroupModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.ts b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.ts
new file mode 100644 (file)
index 0000000..42bc6e6
--- /dev/null
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { CreateGroupModalComponent } from './create-group-modal.component';\r
+import { FormsModule } from '@angular/forms';\r
+import { MatButtonModule, MatInputModule, MatSelectModule, MatOptionModule, MatSnackBarModule, MatIconModule, MatDialogModule } from '@angular/material';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+import { PageHeaderModule } from '..';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatSnackBarModule,\r
+    PageHeaderModule,\r
+    AlertSnackbarModule,\r
+    MatIconModule,\r
+    MatDialogModule\r
+  ],\r
+  declarations: [CreateGroupModalComponent],\r
+  exports: [ CreateGroupModalComponent],\r
+  entryComponents: [ CreateGroupModalComponent ]\r
+})\r
+export class CreateGroupModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.pug b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.pug
new file mode 100644 (file)
index 0000000..dbafe0e
--- /dev/null
@@ -0,0 +1,167 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+form(#testDefinitionForm="ngForm")\r
+  .row.mb-3\r
+    .col-sm-6(style="justify-content: flex-end;flex-direction: column;display: flex")\r
+\r
+      //- Diagram\r
+      .row(style="height: 100%")\r
+        //- placeholder\r
+        .col-12(*ngIf="!ptd.currentInstance.bpmnXml", style="text-align:center; opacity: .4")\r
+          i.fa.fa-5x.fa-object-group\r
+        //- diagram\r
+        .col-12(#canvas, [hidden]="!ptd.currentInstance.bpmnXml", style="position: relative; cursor: pointer", (click)="enlargeBpmn()")\r
+          button(mat-icon-button, color="primary", style="position: absolute; top: 0px; right: 0px; z-index: 100")\r
+            mat-icon zoom_in\r
+        \r
+\r
+      //- Upload and version\r
+      .row\r
+        .col-sm-6(style="text-align:center")\r
+          input(id="file", #file, type="file", name="file", ng2FileSelect, [uploader]="bpmnUploader", style="display:none", [hidden]="!ptd.currentInstance.isDeployed", (change)="validateFile()", required)\r
+          \r
+          //- when creating new\r
+          button(mat-raised-button, color="accent", *ngIf="!ptd.currentInstance.isDeployed && !ptd.currentInstance.bpmnXml && isNew", [hidden]="isUploading", (click)="isClicked = true", onclick="file.click();")\r
+            | Upload Workflow\r
+          button(mat-raised-button, color="primary", *ngIf="!ptd.currentInstance.isDeployed && ptd.currentInstance.bpmnXml", [hidden]="isUploading", onclick="file.click();")\r
+            | Change Workflow\r
+\r
+          //- when editing\r
+          //- button(mat-raised-button, color="primary", *ngIf="!isNew && ptd.currentInstance.isDeployed", [hidden]="isUploading", (click)="newVersion(this.ptd.processDefinitionKey)", onclick="file.click();")\r
+          //-   | New Version\r
+          h4(*ngIf="ptd.currentInstance.isDeployed") Deployed\r
+          \r
+          mat-spinner(style="margin:auto", [diameter]="30", [hidden]="!isUploading")\r
+\r
+        .col-sm-6\r
+          mat-form-field(*ngIf="ptd.processDefinitionKey != null")\r
+            input(matInput, placeholder="Process Definition Key", name="processDefinitionKey", maxlength="22", [disabled]="hasBeenSaved", (keyup)="checkProcessDefinitionKey()", [(ngModel)]="ptd.processDefinitionKey", required)\r
+            mat-spinner(matSuffix, *ngIf="pStatus == 'loading'", [diameter]="19")\r
+            mat-icon(matSuffix, *ngIf="pStatus == 'unique'", style="color: green") check\r
+            mat-icon(matSuffix, *ngIf="pStatus == 'notUnique'", style="color: red") error_outline\r
+\r
+    .col-sm-6\r
+      mat-form-field(style="width:100%")\r
+        input(matInput, type="text", placeholder="Name", name="name", [disabled]="(existingTd && !hasBeenSaved) || !ptd.currentInstance.bpmnXml", [(ngModel)]="ptd.testName", required)\r
+        mat-error Required\r
+      mat-form-field(style="width:100%")\r
+        input(matInput, type="text", placeholder="Description", name="description", [disabled]="(existingTd && !hasBeenSaved) || !ptd.currentInstance.bpmnXml", [(ngModel)]="ptd.testDescription", required)\r
+        mat-error Required\r
+      //- mat-form-field(style="width:100%")\r
+      //-   mat-select((selectionChange)="markAsDirty()", name="ns", [disabled]="(existingTd && !hasBeenSaved) || !ptd.currentInstance.bpmnXml", placeholder="Group", [(value)]="ptd.groupId", required)\r
+      //-     mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}\r
+      //-   mat-error Required\r
+      mat-form-field(style="width:100%")\r
+        input(matInput, type="text", *ngIf="!hasBeenSaved", placeholder="Version", name="version", [(ngModel)]="ptd.currentInstance.version", (keyup)="checkVersionUnique()", required)\r
+        mat-select((selectionChange)="switchVersion(ptd.currentVersionName)", placeholder="Version", name="selectedVersion", *ngIf="hasBeenSaved", [(value)]="ptd.currentVersionName", required)\r
+          mat-option(*ngFor="let instance of ptd.bpmnInstances.slice().reverse()", value="{{instance.version}}") {{ instance.version }}\r
+        mat-error Required\r
+        button(mat-button, matSuffix, color="primary", *ngIf="hasBeenSaved", (click)="newVersion(this.ptd.processDefinitionKey)", onclick="file.click();") New\r
+\r
+      button(mat-button, (click)="viewer.download()", color="primary")\r
+        mat-icon cloud_download\r
+        span.ml-2 Download\r
+        \r
+      \r
+  .row\r
+    .col-12(*ngIf="ptd.currentInstance")\r
+      mat-accordion\r
+        mat-expansion-panel([expanded]="ptd.currentInstance.dataTestHeads.length > 0")\r
+          mat-expansion-panel-header\r
+            mat-panel-title Test Heads\r
+            mat-panel-description(*ngIf="ptd.currentInstance.dataTestHeads") {{ ptd.currentInstance.dataTestHeads.length > 0? ptd.currentInstance.dataTestHeads.length : '' }}\r
+          .ps(style="position: relative; max-height: 105px", [perfectScrollbar])\r
+            div(style="white-space: nowrap")\r
+              .mr-4.text-center(*ngFor=("let task of ptd.currentInstance.dataTestHeads; index as i; trackBy: trackByFn"), style="display:inline-block")\r
+                .text-muted {{task.bpmnVthTaskId}}\r
+                button(color="accent", mat-fab, (click)="selectTestHead(i)")\r
+                  i.fa.fw.fa-gears.fa-2x(style="color:white")\r
+                p.text-muted {{ (task.testHead && task.testHead.testHeadName) ? task.testHead.testHeadName : '' }}\r
+      mat-expansion-panel([expanded]="true")\r
+        mat-expansion-panel-header\r
+          mat-panel-title Resources\r
+          mat-panel-description A single .zip file with scripts\r
+        input(type="file", #scripts, id="scripts", name="scripts", hidden,  (change)="markAsDirty()", ng2FileSelect, [uploader]="uploader", accept="application/zip")\r
+        .row(*ngIf="ptd.currentInstance.resourceFileId")\r
+          .col-12\r
+            mat-list\r
+              mat-list-item\r
+                mat-icon(mat-list-icon) insert_drive_file\r
+                h4(mat-line) {{ptd.currentInstance.resourceFileName }}\r
+        .row(*ngIf="!ptd.currentInstance.isDeployed")\r
+          .col-md-3\r
+            //- .mb-2 TESTING GIT TRACKING\r
+            //-   | Multiple Files \r
+            //-   mat-slide-toggle(color="primary", name="isZip", [(ngModel)]="isZip", (change)="uploader.clearQueue()")\r
+            //-   |  .zip\r
+            //- div \r
+            //-   input(*ngIf="!isZip", type="file", name="scripts", ng2FileSelect, [uploader]="uploader", multiple)\r
+            \r
+            button(mat-raised-button, *ngIf="isZip && !ptd.currentInstance.resourceFileId", onclick="scripts.click()", color="primary") Choose File\r
+            button(mat-raised-button, *ngIf="isZip && ptd.currentInstance.resourceFileId", onclick="scripts.click()", color="primary") Replace File\r
+          .col-md-8.ml-2\r
+            div(*ngIf="uploader.queue.length > 0")\r
+              label File:\r
+              ul.list-group(style="position:relative")\r
+                li.list-group-item(*ngFor="let item of uploader.queue")\r
+                  | {{ item?.file?.name }}\r
+                  div.upload-progress([ngStyle]="{'width': item.progress + '%'}")\r
+              //- button.pull-right(mat-button, (click)="upload()") Upload All\r
+              label(*ngIf="ptd.currentInstance.resourceFileId && uploader.queue.length > 0 && !saved") This will replace the previous resouce file\r
+              button.pull-right(mat-button, color="primary", (click)="clearQueue()") Remove All\r
+        .row(*ngIf="ptd.currentInstance.isDeployed")\r
+          .col-12(*ngIf="!ptd.currentInstance.resourceFileId")\r
+            | No resources were deployed with this version\r
+            \r
+\r
+    .col-12(*ngIf="ptd.currentInstance.testDataTemplate != {}")\r
+      h5.text-muted.mt-4 testInputTemplate.yaml\r
+      div(style="border: 1px solid lightgrey; font-size: 16px !important")\r
+        codemirror([config]="codeConfig", [(ngModel)]='ptd.currentInstance.testDataTemplate', name="testConfig")\r
+\r
+  .row(style="height:30px")\r
+  .row.form-buttons\r
+    .col-12.mt-3\r
+      .pull-left\r
+        .mr-3(mat-button, *ngIf="hasBeenSaved && saved && !testDefinitionForm.dirty") saved\r
+          mat-icon(style="color:green") check\r
+      .pull-right\r
+        //save\r
+        button.mr-3(mat-raised-button, *ngIf="!hasBeenSaved && !inProgress", color="primary", [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml || !successUpload || pStatus == 'notUnique'", (click)="save()") Save\r
+\r
+        //update\r
+        button.mr-3(mat-raised-button, *ngIf="hasBeenSaved && !inProgress", color="primary", [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml  || !testDefinitionForm.dirty || !successUpload", (click)="update()") Update\r
+        \r
+        //save and deploy\r
+        button.mr-3(mat-raised-button, color="accent",  [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml || !successUpload || pStatus == 'notUnique'", *ngIf="!ptd.currentInstance.isDeployed && !hasBeenSaved && !saved && !inProgress", (click)="saveDeploy()") Save & Deploy\r
+        \r
+        //update and deploy\r
+        button.mr-3(mat-raised-button, color="accent", [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml || !successUpload", *ngIf="!ptd.currentInstance.isDeployed && hasBeenSaved && testDefinitionForm.dirty && !inProgress", (click)="updateDeploy()") Update & Deploy\r
+\r
+        //deploy\r
+        button.mr-3(mat-raised-button, color="accent", [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml || !successUpload", *ngIf="!ptd.currentInstance.isDeployed && hasBeenSaved && testDefinitionForm.pristine && !inProgress", (click)="deploy()") Deploy\r
+      \r
+        //delete\r
+        button.mr-3(mat-raised-button, color="warn", *ngIf="hasBeenSaved && !inProgress", (click)="deleteVersion()") Delete Version\r
+\r
+        //- button((click)="print()") print\r
+        \r
+        //In Progress\r
+        button.mr-3(mat-raised-button, *ngIf="inProgress", color="primary", disabled)\r
+          mat-spinner([diameter]="19", style="display:inline")\r
+          div.ml-4(style="display:inline") In Progress\r
+        \r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.scss b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.scss
new file mode 100644 (file)
index 0000000..124106c
--- /dev/null
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.slider {\r
+       overflow-y: hidden;\r
+       max-height: 500px; /* approximate max height */\r
+\r
+       transition-property: all;\r
+       transition-duration: .5s;\r
+       transition-timing-function: cubic-bezier(0, 1, 0.5, 1);\r
+}\r
+\r
+.fa-spinner{\r
+       color: green;\r
+}\r
+\r
+.tsd {\r
+       width:100%; \r
+       height:100%; \r
+       background: orange\r
+}\r
+\r
+.centered {\r
+       position: absolute;\r
+       left: 50%;\r
+       top: 50%;\r
+       -webkit-transform: translate(-50%, -50%);\r
+       transform: translate(-50%, -50%);\r
+}\r
+\r
+.no-margin-tlb {\r
+       margin: -16px -16px -16px -16px;\r
+}\r
+\r
+.mbtn:focus {\r
+       outline: none;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.spec.ts b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.spec.ts
new file mode 100644 (file)
index 0000000..595b3d1
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { CreateTestFormComponent } from './create-test-form.component';\r
+\r
+describe('CreateTestFormComponent', () => {\r
+  let component: CreateTestFormComponent;\r
+  let fixture: ComponentFixture<CreateTestFormComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ CreateTestFormComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(CreateTestFormComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.ts b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.ts
new file mode 100644 (file)
index 0000000..f88523d
--- /dev/null
@@ -0,0 +1,823 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ElementRef, OnDestroy } from '@angular/core';\r
+import { MatDialog, MatSnackBar } from '@angular/material';\r
+import { SelectTestHeadModalComponent } from '../select-test-head-modal/select-test-head-modal.component';\r
+import { GroupService } from '../../services/group.service';\r
+import { TestDefinitionService } from '../../services/test-definition.service';\r
+import { AlertModalComponent } from '../alert-modal/alert-modal.component';\r
+import { Alert } from 'selenium-webdriver';\r
+import { ListService } from '../../services/list.service';\r
+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';\r
+import { TestHeadService } from 'app/shared/services/test-head.service';\r
+import { FileUploader, FileItem, ParsedResponseHeaders } from 'ng2-file-upload';\r
+import { AppGlobals } from 'app/app.global';\r
+import { HttpHeaders } from '@angular/common/http';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { stringify } from '@angular/core/src/render3/util';\r
+import Modeler from 'bpmn-js';\r
+import { FileService } from 'app/shared/services/file.service';\r
+import { FileTransferService } from 'app/shared/services/file-transfer.service';\r
+import { TestDefinition } from './test-definition.class';\r
+import { connectableObservableDescriptor } from 'rxjs/internal/observable/ConnectableObservable';\r
+import { Buffer } from 'buffer';\r
+import { ViewWorkflowModalComponent } from '../view-workflow-modal/view-workflow-modal.component';\r
+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';\r
+import { Bpmn } from 'app/shared/models/bpmn.model';\r
+\r
+\r
+@Component({\r
+  selector: 'app-create-test-form',\r
+  templateUrl: './create-test-form.component.pug',\r
+  styleUrls: ['./create-test-form.component.scss']\r
+})\r
+export class CreateTestFormComponent implements OnInit, OnDestroy {\r
+\r
+\r
+  public codeConfig = {\r
+    mode: 'yaml',\r
+    theme: 'eclipse',\r
+    lineNumbers: true\r
+  };\r
+\r
+  public trackByFn;\r
+\r
+  public selectedTestHead;\r
+  public groups;\r
+  public isUploading;\r
+  public successUpload = false;\r
+  public processDefinitionKey = false;\r
+  public validateResponse;\r
+  public pStatus;\r
+  public file: File;\r
+  public hasBeenSaved = false;\r
+  public saved = false;\r
+  public isClicked;\r
+  public isZip = true;\r
+  public viewer: Bpmn;\r
+  public scriptFiles = [];\r
+  public existingTd;\r
+\r
+  @ViewChild('testDefinitionForm') form: any;\r
+  @ViewChild('canvas') canvas;\r
+  @ViewChild('scripts') scripts: ElementRef;\r
+  @ViewChild('file') bpmnFileInput: ElementRef;\r
+\r
+  @Input() public listKey;\r
+\r
+  @Input() public formData;\r
+\r
+  @Output() public childEvent = new EventEmitter();\r
+\r
+  public uploader: FileUploader;\r
+  public bpmnUploader: FileUploader;\r
+\r
+  public inProgress = false;\r
+\r
+  // New variables\r
+  public ptd: TestDefinition;\r
+  public isNew = true;\r
+\r
+  constructor(\r
+    public dialog: MatDialog,\r
+    private list: ListService,\r
+    private testHead: TestHeadService,\r
+    private group: GroupService,\r
+    private testDefinition: TestDefinitionService,\r
+    private snack: MatSnackBar,\r
+    private cookie: CookieService,\r
+    private fileTransfer: FileTransferService,\r
+    private fileService: FileService,\r
+    private bpmnFactory: BpmnFactoryService\r
+    ) { }\r
+\r
+  print(){\r
+    console.log(this.ptd);\r
+  }\r
+\r
+  async ngOnInit() {\r
+    //this.setNew();\r
+\r
+    this.viewer = await this.bpmnFactory.setup({\r
+      mode: 'viewer',\r
+      options: {\r
+        container: this.canvas.nativeElement\r
+      }\r
+    })\r
+    \r
+    this.ptd = new TestDefinition();\r
+    this.ptd.reset();\r
+    this.ptd.switchVersion();\r
+\r
+    let uploadOptions = {\r
+      url: AppGlobals.baseAPIUrl + 'file-transfer',\r
+      authTokenHeader: 'Authorization',\r
+      authToken: 'Bearer ' + JSON.parse(this.cookie.get('access_token'))\r
+    };\r
+\r
+    //File Uploaders\r
+    this.uploader = new FileUploader(uploadOptions);\r
+    this.bpmnUploader = new FileUploader(uploadOptions);\r
+\r
+    if (this.formData && this.formData !== 'new') {\r
+      this.hasBeenSaved = true;\r
+      this.successUpload = true;\r
+      this.isNew = false;\r
+      this.setTestDefinition();\r
+    }\r
+\r
+    this.group.find({$limit: -1}).subscribe((x) => {\r
+      this.groups = x;\r
+    });\r
+\r
+  }\r
+\r
+  ngOnDestroy(){\r
+    \r
+  }\r
+\r
+  waitSave(){\r
+    return new Promise((resolve, reject) => {\r
+      console.log('waitsave')\r
+      //upload bpmn file\r
+      this.saveBpmnFile().then(bpmnFile => {\r
+        console.log(bpmnFile)\r
+        console.log('pass save bpmnfile')\r
+        this.checkTestDataTemplate();\r
+\r
+        let data = this.gatherTestDefinition(bpmnFile);\r
+        console.log(data)\r
+        //If this is not a new version\r
+        if(!this.existingTd){\r
+          delete data._id;\r
+          \r
+          this.create(data).then(\r
+            result => {\r
+              resolve(result);\r
+              this.setTestDefinition(result);\r
+              this.showHasBeenSaved();\r
+            }\r
+          ).catch(err => {\r
+            reject(err);\r
+            this.showHasNotBeenSaved();\r
+          });\r
+        }else{\r
+          //create version by updating definition\r
+          this.saveVersion(data).then(\r
+            result => {\r
+              resolve(result);\r
+              this.setTestDefinition(result);\r
+              this.showHasBeenSaved();\r
+            }\r
+          ).catch(err => {\r
+            reject(err);\r
+            this.showHasNotBeenSaved();\r
+          });\r
+        }\r
+      });\r
+    })\r
+  }\r
+  \r
+  // Saves Test Definition - Triggered by "Save" button\r
+  save() {\r
+    //set in progress\r
+    this.inProgress = true;\r
+    return this.waitSave();\r
+\r
+  }\r
+\r
+  // Updates Test Definition - Triggered by "Update" button\r
+  update() {\r
+    this.inProgress = true;\r
+    return this.saveBpmnFile().then(bpmnFile => {\r
+      this.checkTestDataTemplate();\r
+\r
+      var data = this.gatherTestDefinition(bpmnFile);\r
+\r
+      return this.testDefinition.patch(data)\r
+        .subscribe(\r
+          result => {\r
+            this.uploadResources(result).then(\r
+              res => {\r
+                this.setTestDefinition(res);\r
+                this.showHasBeenUpdated();\r
+              }\r
+            );\r
+            return result;\r
+          },\r
+          error => {\r
+            this.showHasNotBeenUpdated(error);\r
+          }\r
+        );\r
+    });\r
+  }\r
+\r
+  deleteVersion(){\r
+    let deleteDialog = this.dialog.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: { type: 'confirmation', message: 'Are you sure you want to delete version ' + this.ptd.currentVersionName }\r
+    });\r
+\r
+    deleteDialog.afterClosed().subscribe(\r
+      result => {\r
+        if(result){\r
+          this.inProgress = true;\r
+          if(this.ptd.bpmnInstances.length == 1){\r
+            this.testDefinition.delete(this.ptd._id).subscribe(\r
+              result => {\r
+                this.childEvent.emit();\r
+              }\r
+            )\r
+          }else{\r
+            this.ptd.removeBpmnInstance(this.ptd.currentVersionName);\r
+            this.update().then(\r
+              res => {\r
+                this.inProgress = false;\r
+              }\r
+            );\r
+          }\r
+        }\r
+      }\r
+    )\r
+  }\r
+\r
+  // Deploys Test Definition version - Triggerd by "Deploy" button\r
+  deploy(versionName?) {\r
+    this.inProgress = true;\r
+    //console.log(this.ptd)\r
+    this.testDefinition.deploy(this.ptd, versionName)\r
+      .subscribe(result => {\r
+        \r
+        this.handleResponse(result);\r
+        this.inProgress = false;\r
+        if (result['statusCode'] == 200) {\r
+          this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            duration: 1500,\r
+            data: {\r
+              message: 'Test Definition Deployed!'\r
+            }\r
+          });\r
+          this.ptd.currentInstance.isDeployed = true;\r
+        } else {\r
+          this.dialog.open(AlertModalComponent, {\r
+            width: '250px',\r
+            data: {\r
+              type: 'Alert',\r
+              message: JSON.stringify(result)\r
+            }\r
+          });\r
+        }\r
+      },\r
+        err => {\r
+          this.inProgress = false;\r
+        }\r
+\r
+      );\r
+  }\r
+\r
+  create(data){\r
+    return new Promise((resolve, reject) => {\r
+      this.testDefinition.create(data)\r
+        .subscribe(\r
+          result => {\r
+            this.uploadResources(result).then(\r
+              res => {\r
+                resolve(res);\r
+              }\r
+            );\r
+          },\r
+          error => {\r
+            this.dialog.open(AlertModalComponent, {\r
+              width: '250px',\r
+              data: {\r
+                type: 'Alert',\r
+                message: JSON.stringify(error)\r
+              }\r
+            });\r
+            reject(error);\r
+          }\r
+        );\r
+    });\r
+  }\r
+\r
+  newVersion(processDefinitionKey){\r
+    this.hasBeenSaved = false;\r
+    this.isNew = true;\r
+    this.ptd.reset();\r
+    this.ptd.switchVersion();\r
+    this.ptd.setProcessDefinitionKey(processDefinitionKey);\r
+  }\r
+\r
+  checkProcessDefinitionKey() {\r
+    this.pStatus = 'loading';\r
+    this.testDefinition.check(this.ptd.getProcessDefinitionKey()).subscribe(result => {\r
+      console.log(result);\r
+      if (result['statusCode'] == 200) {\r
+        this.pStatus = 'unique';\r
+      } else {\r
+        this.pStatus = 'notUnique';\r
+      }\r
+\r
+      this.ptd.bpmnInstances = this.ptd.bpmnInstances.filter((e, i) => {\r
+        return i == 0;\r
+      })\r
+      \r
+      // this.ptd.bpmnInstances.forEach((elem, val) => {\r
+      //   if(val > 0){\r
+      //     this.ptd.bpmnInstances.splice(val, 1);\r
+      //   }\r
+      // })\r
+\r
+      //New Code\r
+      if(result['body'] && result['body'][0]){\r
+        //when changing bpmn dont\r
+        //if(this.ptd.currentInstance.isDeployed){\r
+          let res = result['body'][0];\r
+          this.existingTd = true;\r
+          this.ptd.setId(res._id);\r
+          this.ptd.setName(res.testName);\r
+          this.ptd.setDescription(res.testDescription);\r
+          this.ptd.setGroupId(res.groupId);\r
+          this.ptd.setVersion(res.bpmnInstances.length + 1);\r
+          //this.ptd.bpmnInstances = [];\r
+\r
+          for(let i = 0; i < res.bpmnInstances.length; i++){\r
+            this.ptd.addBpmnInstance(res.bpmnInstances[i]);\r
+          }\r
+\r
+          \r
+          //this.ptd.addBpmnInstance (res.bpmnInstances);\r
+        //}\r
+      }else{\r
+        this.existingTd = false;\r
+        this.ptd.setId(null);\r
+        this.ptd.setName('');\r
+        this.ptd.setDescription('');\r
+        this.ptd.setGroupId('');\r
+        this.ptd.setVersion(1);\r
+      }\r
+\r
+      if(!this.ptd.currentInstance.version){\r
+        this.ptd.setNewVersion();\r
+      }\r
+\r
+    });\r
+  }\r
+\r
+  validateFile() {\r
+\r
+    this.isUploading = true\r
+    this.fetchFileContents(val => {\r
+      //\r
+      this.ptd.currentInstance.bpmnXml = val;\r
+      if (!this.ptd.currentInstance.bpmnXml) {\r
+        this.isUploading = false;\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '250px',\r
+          data: {\r
+            type: 'Alert',\r
+            message: 'File was not selected. Please try again.'\r
+          }\r
+        });\r
+        return null;\r
+      }\r
+      \r
+      this.testDefinition.validate(this.ptd.getAll())\r
+        .subscribe(\r
+          result => {\r
+            this.handleResponse(result);\r
+            //\r
+            this.isUploading = false;\r
+            this.ptd.currentInstance.bpmnHasChanged = true;\r
+            this.loadDiagram();\r
+          },\r
+          err => {\r
+            this.dialog.open(AlertModalComponent, {\r
+              width: '250px',\r
+              data: {\r
+                type: 'Alert',\r
+                message: 'Something went wrong. Please try again'\r
+              }\r
+            });\r
+            this.isUploading = false;\r
+          }\r
+        );\r
+    });\r
+\r
+\r
+  }\r
+\r
+  showHasNotBeenSaved(){\r
+    this.dialog.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'Alert',\r
+        message: 'There was a problem with saving the test definition.'\r
+      }\r
+    });\r
+    this.inProgress = false;\r
+  }\r
+\r
+  showHasBeenSaved(){\r
+    this.snack.openFromComponent(AlertSnackbarComponent, {\r
+      duration: 1500,\r
+      data: {\r
+        message: 'Test Definition Saved!'\r
+      }\r
+    });\r
+    //this.switchVersion();\r
+    this.ptd.switchVersion();\r
+    this.hasBeenSaved = true;\r
+    this.saved = true;\r
+    this.form.form.markAsPristine();\r
+    this.inProgress = false;\r
+  }\r
+\r
+  showHasBeenUpdated(){\r
+    this.snack.openFromComponent(AlertSnackbarComponent, {\r
+      duration: 1500,\r
+      data: {\r
+        message: 'Test Definition Updated!'\r
+      }\r
+    });\r
+    //this.switchVersion();\r
+    this.ptd.switchVersion(this.ptd.currentInstance.version);\r
+    this.saved = true;\r
+    this.form.form.markAsPristine();\r
+    this.ptd.currentInstance.bpmnHasChanged = false;\r
+    this.inProgress = false;\r
+  }\r
+\r
+  showHasNotBeenUpdated(error = null){\r
+    this.dialog.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'Alert',\r
+        message: JSON.stringify(error)\r
+      }\r
+    });\r
+    this.inProgress = false;\r
+  }\r
+\r
+  setTestDefinition(data = null){\r
+    //new\r
+    if(data){\r
+      \r
+      this.ptd.setAll(data);\r
+    }else{\r
+      this.ptd.setAll(JSON.parse(JSON.stringify(this.formData)));\r
+    }\r
+\r
+    this.switchVersion();\r
+\r
+    //console.log(this.ptd);\r
+    \r
+  }\r
+\r
+  clearQueue(){\r
+    this.uploader.clearQueue();\r
+    if(this.scripts){\r
+      this.scripts.nativeElement.value = null;\r
+    }\r
+  }\r
+\r
+  switchVersion(versionName = null){\r
+    this.ptd.switchVersion(versionName);\r
+    this.checkTestDataTemplate();\r
+\r
+    this.clearQueue();\r
+    this.bpmnFileInput.nativeElement.value = null;\r
+\r
+    //Get bpmn file contents\r
+    this.fileTransfer.get(this.ptd.currentInstance.bpmnFileId).subscribe(\r
+      result => {\r
+        result = new Buffer(result as Buffer);\r
+        this.ptd.currentInstance.bpmnXml = result.toString();\r
+        this.loadDiagram();\r
+      }\r
+    );\r
+\r
+    //get info on resource file\r
+    if(this.ptd.currentInstance.resourceFileId){\r
+      this.fileService.get(this.ptd.currentInstance.resourceFileId).subscribe(\r
+        result => {\r
+          this.ptd.currentInstance.resourceFileName = result['filename'];\r
+        }\r
+      )\r
+    }\r
+\r
+    if(this.ptd.currentInstance.testHeads){\r
+      this.ptd.currentInstance.dataTestHeads = [];\r
+      this.ptd.currentInstance.testHeads.forEach((elem, val) => {\r
+        //Find test head info\r
+        const e = elem;\r
+        this.testHead.get(e.testHeadId).subscribe(\r
+          result => {\r
+            this.ptd.currentInstance.dataTestHeads.push({\r
+              testHeadId: e.testHeadId,\r
+              bpmnVthTaskId: e.bpmnVthTaskId,\r
+              testHead: JSON.parse(JSON.stringify(result))\r
+            });\r
+          },\r
+          err => {\r
+            this.ptd.currentInstance.dataTestHeads.push({\r
+              testHeadId: e.testHeadId,\r
+              bpmnVthTaskId: e.bpmnVthTaskId,\r
+              testHead: { _id: e.testHeadId, testHeadName: 'No Access' }\r
+            });\r
+          }\r
+        );\r
+      });\r
+    }\r
+  }\r
+\r
+  gatherTestDefinition(bpmnFile = null) {\r
+\r
+    if(bpmnFile){\r
+      this.ptd.currentInstance.bpmnFileId = bpmnFile._id;\r
+    }\r
+\r
+    this.ptd.currentInstance.testHeads = [];\r
+    this.ptd.currentInstance.dataTestHeads.forEach((elem, val) => {\r
+      this.ptd.currentInstance.testHeads.push({\r
+        testHeadId: elem.testHead._id,\r
+        bpmnVthTaskId: elem.bpmnVthTaskId\r
+      });\r
+    });\r
+\r
+    return this.ptd.getAll();\r
+  \r
+  }\r
+\r
+  saveDeploy() {\r
+    let version = JSON.parse(JSON.stringify(this.ptd.currentInstance.version));\r
+    console.log(version)\r
+    this.save().then(x => {\r
+      this.deploy(version);\r
+    });\r
+  }\r
+\r
+  updateDeploy() {\r
+    let version = JSON.parse(JSON.stringify(this.ptd.currentInstance.version));\r
+    this.update().then(x => {\r
+      this.deploy(version);\r
+    });\r
+  }S\r
+\r
+  handleResponse(result) {\r
+    this.successUpload = true;\r
+    this.processDefinitionKey = false;\r
+    //this.validateResponse = result;\r
+    if (result['body']['errors']) {\r
+\r
+\r
+      if (result['body']['errors']['processDefinitionKey']) {\r
+        this.openProcessDefinitionKeyModal();\r
+        this.pStatus = 'notUnique';\r
+        this.ptd.setProcessDefinitionKey(result['body'].errors.processDefinitionKey.key)\r
+        //this.td.processDefinitionKey = result['body']['errors']['processDefinitionKey']['key'];\r
+        this.processDefinitionKey = true;\r
+      }\r
+      if (result['body']['errors']['notFound']) {\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '250px',\r
+          data: { type: 'alert', message: result['body']['errors']['notFound']['error'] }\r
+        });\r
+        this.successUpload = false;\r
+      }\r
+      if (result['body']['errors']['startEvent']) {\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '250px',\r
+          data: { type: 'alert', message: result['body']['errors']['startEvent']['error'] }\r
+        });\r
+        this.successUpload = false;\r
+      }\r
+      if (result['body']['errors']['required']) {\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '250px',\r
+          data: { type: 'alert', message: result['body']['errors']['required']['error'] }\r
+        });\r
+        this.successUpload = false;\r
+      }\r
+      if (result['body']['errors']['permissions']) {\r
+        let mess = '';\r
+        result['body']['errors']['permissions'].forEach(elem => {\r
+          mess += elem.error + '\n';\r
+        })\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '250px',\r
+          data: { type: 'alert', message: mess }\r
+        });\r
+        this.successUpload = false;\r
+      }\r
+\r
+    }else{\r
+      this.markAsDirty();\r
+    }\r
+    // Update list of test heads\r
+    if (result['body']['bpmnVthTaskIds']) {\r
+      this.ptd.currentInstance.dataTestHeads = result['body'].bpmnVthTaskIds;\r
+      this.ptd.currentInstance.testHeads = [];\r
+      //this.definitionInstance.testHeads = result['body']['bpmnVthTaskIds'];\r
+    }\r
+\r
+    //Update plfos list\r
+    if(result['body']['bpmnPfloTaskIds']){\r
+      this.ptd.currentInstance.pflos = result['body'].bpmnPfloTaskIds;\r
+    }\r
+\r
+    if (result['body']['processDefinitionKey']) {\r
+      this.ptd.setProcessDefinitionKey(result['body'].processDefinitionKey);\r
+      //this.td.processDefinitionKey = result['body']['processDefinitionKey'];\r
+      this.checkProcessDefinitionKey()\r
+    }\r
+  }\r
+\r
+  markAsDirty() {\r
+    this.form.control.markAsDirty();\r
+  }\r
+\r
+  //returns promise for file object \r
+  saveBpmnFile() {\r
+    return new Promise((resolve, reject) => {\r
+\r
+      //check for bpmnXml\r
+      if (!this.ptd.currentInstance.bpmnXml) {\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '250px',\r
+          data: {\r
+            type: 'Alert',\r
+            message: 'No File found. Please select a file to upload'\r
+          }\r
+        });\r
+        reject();\r
+      }\r
+\r
+      if(this.ptd.currentInstance.bpmnHasChanged){\r
+        // Upload\r
+        console.log('validate save call')\r
+        this.testDefinition.validateSave(this.ptd).subscribe(\r
+          result => {\r
+            resolve(JSON.parse(result.toString())[0]);\r
+          }\r
+        );\r
+      }else{\r
+        //bpmn has not changed, so did not save it.\r
+        resolve(null);\r
+      }\r
+    });\r
+  }\r
+\r
+  saveVersion(data){\r
+    return new Promise((resolve, reject) => {\r
+\r
+      let newBpmnInsance = JSON.parse(JSON.stringify(data.bpmnInstances[0]));\r
+      delete data.bpmnInstances;\r
+      data['$push'] = {\r
+        bpmnInstances: newBpmnInsance\r
+      }\r
+\r
+      console.log(data)\r
+\r
+      this.testDefinition.patch(data).subscribe(\r
+        result => {\r
+          this.uploadResources(result).then(\r
+            res => {\r
+              resolve(res);\r
+            }\r
+          )\r
+        },\r
+        err => {\r
+          reject(err);\r
+        }\r
+      )\r
+    });\r
+  }\r
+\r
+  uploadResources(td){\r
+    return new Promise((resolve, reject) => {\r
+      if(this.uploader.queue.length > 0){\r
+        //console.log('has file');\r
+        this.uploader.uploadAll();\r
+        this.uploader.onCompleteItem = (item: FileItem, response: string, status: Number, headers: ParsedResponseHeaders) => {\r
+          this.scriptFiles.push(JSON.parse(response)[0]);\r
+          //console.log('in file')\r
+        }\r
+        this.uploader.onCompleteAll = () => {\r
+          //console.log('complete')\r
+          let scriptFilesId = [];\r
+          for (let i = 0; i < this.scriptFiles.length; i++) {\r
+            scriptFilesId.push(this.scriptFiles[i]['_id']);\r
+          }\r
+          td['bpmnInstances'][this.ptd.currentVersion]['resourceFileId'] = scriptFilesId[0];\r
+          //console.log(td);\r
+          this.testDefinition.patch(td).subscribe(\r
+            res => {\r
+              //console.log(res);\r
+              resolve(res);\r
+            },\r
+            err => {\r
+              reject(err);\r
+            }\r
+          );\r
+        }\r
+      }else{\r
+        resolve(td);\r
+      }\r
+    });\r
+  }\r
+\r
+  checkTestDataTemplate() {\r
+    if (this.ptd.currentInstance.testDataTemplate == null || this.ptd.currentInstance.testDataTemplate == '') {\r
+      delete this.ptd.currentInstance.testDataTemplate;\r
+    }\r
+    // if (this.definitionInstance.testDataTemplate == null || this.definitionInstance.testDataTemplate == '') {\r
+    //   delete this.definitionInstance.testDataTemplate;\r
+    // }\r
+  }\r
+\r
+  async loadDiagram() {\r
+    if (this.ptd.currentInstance.bpmnXml) {\r
+      //render xml and display\r
+      this.viewer.setBpmnXml(this.ptd.currentInstance.bpmnXml);\r
+      // if (!this.viewer) {\r
+      //   this.viewer = new Modeler({\r
+      //     container: this.canvas.nativeElement\r
+      //   });\r
+      // }\r
+\r
+      // this.viewer.importXML(this.ptd.currentInstance.bpmnXml, (err) => {\r
+      //   if (!err) {\r
+      //     this.viewer.get('canvas').zoom('fit-viewport');\r
+      //   } else {\r
+      //     //\r
+      //   }\r
+      // });\r
+\r
+    }\r
+  }\r
+\r
+  enlargeBpmn(){\r
+    this.dialog.open(ViewWorkflowModalComponent, {\r
+      data: {\r
+        xml: this.ptd.currentInstance.bpmnXml\r
+      },\r
+      width: '100%',\r
+      height: '100%'\r
+    })\r
+  }\r
+\r
+  fetchFileContents(callback) {\r
+    var val = "x";\r
+    var fileToLoad = (document.getElementById('file'))['files'][0];\r
+    var fileReader = new FileReader();\r
+    if (!fileToLoad) {\r
+      return null;\r
+    }\r
+    fileReader.onload = function (event) {\r
+      //\r
+      val = event.target['result'] as string;\r
+\r
+      //\r
+      callback(val);\r
+    }\r
+    fileReader.readAsText(fileToLoad);\r
+  }\r
+\r
+  openProcessDefinitionKeyModal() {\r
+    const dialogRef = this.dialog.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: { type: 'warning', message: 'You cannot use this process definition key. Please change it.' }\r
+    });\r
+  }\r
+\r
+  checkVersionUnique(){\r
+    let exists = false;\r
+    this.ptd.bpmnInstances.forEach(elem => {\r
+      if(elem != this.ptd.currentInstance && elem.version == this.ptd.currentInstance.version){\r
+        exists = true;\r
+      }\r
+    });\r
+\r
+    if(exists){\r
+      this.form.controls['version'].setErrors({error: 'Version Already Exists'});\r
+    }else{\r
+      this.form.controls['version'].setErrors(null);\r
+    }\r
+  }\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.spec.ts b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.spec.ts
new file mode 100644 (file)
index 0000000..595b3d1
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { CreateTestFormComponent } from './create-test-form.component';\r
+\r
+describe('CreateTestFormComponent', () => {\r
+  let component: CreateTestFormComponent;\r
+  let fixture: ComponentFixture<CreateTestFormComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ CreateTestFormComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(CreateTestFormComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.ts b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.ts
new file mode 100644 (file)
index 0000000..7466c81
--- /dev/null
@@ -0,0 +1,77 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { CreateTestFormComponent } from './create-test-form.component';\r
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';\r
+import { MatButtonModule, MatIconModule, MatTooltipModule, MatInputModule, MatBadgeModule, MatOptionModule, MatSelectModule,\r
+    MatSnackBarModule, \r
+    MatSlideToggleModule,\r
+    MatListModule} from '@angular/material';\r
+import { MatProgressButtonsModule} from 'mat-progress-buttons';\r
+import { PageHeaderModule } from '../page-header/page-header.module';\r
+import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG, PerfectScrollbarConfigInterface } from 'ngx-perfect-scrollbar';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { SelectStrategyModalModule } from '../select-strategy-modal/select-strategy-modal.module';\r
+import { SelectTestHeadModalModule } from '../select-test-head-modal/select-test-head-modal.module';\r
+import { CodemirrorModule } from 'ng2-codemirror';\r
+import { MatExpansionModule} from '@angular/material/expansion';\r
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\r
+import { AlertModalModule } from '../alert-modal/alert-modal.module';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+import { FileUploadModule } from 'ng2-file-upload';\r
+import { Bpmn } from 'app/shared/models/bpmn.model';\r
+\r
+const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {\r
+  suppressScrollY: true\r
+};\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FilterPipeModule,\r
+    FormsModule,\r
+    ReactiveFormsModule,\r
+    PageHeaderModule,\r
+    PerfectScrollbarModule,\r
+    MatButtonModule,\r
+    SelectTestHeadModalModule,\r
+    SelectStrategyModalModule,\r
+    MatIconModule,\r
+    CodemirrorModule,\r
+    MatTooltipModule,\r
+    MatInputModule,\r
+    MatExpansionModule,\r
+    MatProgressSpinnerModule,\r
+    MatBadgeModule,\r
+    AlertModalModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    AlertSnackbarModule,\r
+    MatSnackBarModule,\r
+    FileUploadModule,\r
+    MatSlideToggleModule,\r
+    MatProgressButtonsModule,\r
+    MatListModule\r
+  ],\r
+  declarations: [CreateTestFormComponent],\r
+  exports: [CreateTestFormComponent],\r
+  providers: [\r
+    { provide: PERFECT_SCROLLBAR_CONFIG, useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG }\r
+  ]\r
+})\r
+export class CreateTestFormModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/definition-instance.class.ts b/otf-frontend/client/src/app/shared/modules/create-test-form/definition-instance.class.ts
new file mode 100644 (file)
index 0000000..d73e48a
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export class DefinitionInstance {\r
+\r
+    public bpmnFileId: String;\r
+    public bpmnXml: any;\r
+    public resourceFileId: String;\r
+    public resourceFileName: String;\r
+    public isDeployed: Boolean;\r
+    public testHeads: TestHead[];\r
+    public dataTestHeads: DataTestHead[];\r
+    public testDataTemplate: String;\r
+    public testDataTemplateJSON: any;\r
+    public version: String;\r
+    public bpmnHasChanged: Boolean;\r
+    public pflos: Pflo[];\r
+\r
+    constructor(){\r
+        this.testDataTemplate = '';\r
+        this.version = '';\r
+        this.testHeads = [];\r
+        this.dataTestHeads = [];\r
+        this.pflos = [];\r
+        this.isDeployed = false;\r
+        this.bpmnFileId = null;\r
+        this.resourceFileName = null;\r
+        this.bpmnXml = null;\r
+        this.resourceFileId = null;\r
+        this.bpmnHasChanged = false;\r
+    }\r
+\r
+}\r
+\r
+interface TestHead {\r
+    bpmnVthTaskId: String;\r
+    testHeadId: String;\r
+}\r
+\r
+interface DataTestHead extends TestHead {\r
+    testHead: any;\r
+}\r
+\r
+interface Pflo {\r
+    bpmnPfloTaskId: String;\r
+    label: String;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-form/test-definition.class.ts b/otf-frontend/client/src/app/shared/modules/create-test-form/test-definition.class.ts
new file mode 100644 (file)
index 0000000..0303d13
--- /dev/null
@@ -0,0 +1,222 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { DefinitionInstance } from "./definition-instance.class";\r
+import { element } from "@angular/core/src/render3/instructions";\r
+\r
+export class TestDefinition {\r
+\r
+    public _id: String;\r
+    public testName: String;\r
+    public testDescription: String;\r
+    public groupId: String;\r
+    public processDefinitionKey: String;\r
+\r
+    public bpmnInstances: DefinitionInstance[];\r
+\r
+    public currentVersion; // int Array index of the bpmnInstances\r
+    public currentVersionName;\r
+    public currentInstance: DefinitionInstance;\r
+\r
+    constructor(testDefinition: TestDefinition = null){\r
+        if(testDefinition){\r
+            this.setAll(testDefinition);\r
+        }\r
+    }\r
+\r
+\r
+    reset(){\r
+        this._id = '';\r
+        this.testName = '';\r
+        this.testDescription = '';\r
+        this.groupId = '';\r
+        this.processDefinitionKey = '';\r
+        this.bpmnInstances = [\r
+            this.newInstance() as DefinitionInstance\r
+        ];\r
+        this.currentInstance = this.bpmnInstances[0];\r
+        this.currentVersion = 0;\r
+    }\r
+\r
+    getAll(){\r
+        return {\r
+            _id: this._id,\r
+            testName: this.testName,\r
+            testDescription: this.testDescription,\r
+            processDefinitionKey: this.processDefinitionKey,\r
+            bpmnInstances: this.bpmnInstances,\r
+            currentVersion: this.currentVersion\r
+        };\r
+    }\r
+\r
+    switchVersion(version: String = null){\r
+        \r
+        if(version){\r
+            //find the version\r
+            this.bpmnInstances.forEach((elem, val) => {\r
+                if(elem['version'] == version){\r
+                    this.currentVersion = val;\r
+                    this.currentInstance = this.bpmnInstances[val];\r
+                    this.currentVersionName = this.currentInstance.version;\r
+                }\r
+            });\r
+        }else{\r
+            //get latest version\r
+            this.currentVersion = this.bpmnInstances.length - 1;\r
+            this.currentInstance = this.bpmnInstances[this.currentVersion];\r
+            this.currentVersionName = this.currentInstance.version;\r
+        }\r
+    }\r
+\r
+    getVersionKey(){\r
+        return this.currentVersion;\r
+    }\r
+\r
+    //Setter Methods\r
+\r
+    setAll(td){\r
+        this._id = td._id;\r
+        this.testName = td.testName;\r
+        this.testDescription = td.testDescription;\r
+        this.groupId = td.groupId;\r
+        this.processDefinitionKey = td.processDefinitionKey;\r
+        this.setBpmnInstances(td.bpmnInstances);\r
+\r
+        this.bpmnInstances.forEach((elem, val) => {\r
+            if(!elem.dataTestHeads)\r
+                this.bpmnInstances[val].dataTestHeads = [];\r
+        })\r
+    }\r
+\r
+    setId(id: String){\r
+        this._id = id;\r
+    }\r
+\r
+    setName(testName: String){\r
+        this.testName = testName;\r
+    }\r
+\r
+    setDescription(testDescription: String){\r
+        this.testDescription = testDescription;\r
+    }\r
+\r
+    setGroupId(groupId: String){\r
+        this.groupId = groupId;\r
+    }\r
+\r
+    setProcessDefinitionKey(processDefinitionKey: String){\r
+        this.processDefinitionKey = processDefinitionKey;\r
+    }\r
+\r
+    setBpmnInstances(instances: DefinitionInstance[] = []){\r
+        // this.bpmnInstances = [];\r
+        // for(let i = instances.length - 1; i >= 0; i--){\r
+        //     this.bpmnInstances.push(instances[i]);\r
+        // }\r
+        this.bpmnInstances = instances;\r
+    }\r
+\r
+    setNewVersion(newVersion: number = null){\r
+        if(newVersion == null){\r
+            newVersion = this.bpmnInstances.length;\r
+        }\r
+        if(this.setVersion(newVersion) == -1){\r
+            this.setNewVersion(++newVersion);\r
+        }\r
+        return newVersion;\r
+    }\r
+\r
+    setVersion(version){\r
+        \r
+        this.bpmnInstances.forEach((elem, val) => {\r
+            \r
+            \r
+            if(elem.version == version && this.currentVersion != val ){\r
+                return -1;\r
+            }\r
+        });\r
+        this.currentInstance.version = version;\r
+        return version;\r
+    }\r
+\r
+    addBpmnInstance(instance = null){\r
+        \r
+        if(!instance){\r
+           instance = this.newInstance();\r
+        }\r
+        let alreadyIn = false;\r
+        this.bpmnInstances.forEach((elem, val) => {\r
+            if(elem.version == instance.version && val != 0){\r
+                alreadyIn = true;\r
+            }\r
+        });\r
+        if(!alreadyIn){\r
+            this.bpmnInstances.push(instance);\r
+            this.setNewVersion()\r
+        }\r
+        \r
+    }\r
+\r
+    removeBpmnInstance(version){\r
+        this.bpmnInstances.forEach((elem, val) =>{\r
+            if(elem['version'] == version){\r
+                this.bpmnInstances.splice(val, 1);\r
+            }\r
+        });\r
+    }\r
+\r
+    //Getter Methods\r
+\r
+    getId(){\r
+        return this._id;\r
+    }\r
+\r
+    getName(){\r
+        return this.testName;\r
+    }\r
+\r
+    getDescription(){\r
+        return this.testDescription;\r
+    }\r
+\r
+    getGroupId(){\r
+        return this.groupId;\r
+    }\r
+\r
+    getProcessDefinitionKey(){\r
+        return this.processDefinitionKey;\r
+    }\r
+\r
+    getBpmnInstances(version: String = null){\r
+        if(!version)\r
+            return this.bpmnInstances;\r
+        \r
+        this.bpmnInstances.forEach((elem, val) => {\r
+            if(elem['version'] == version){\r
+                return elem;\r
+            }\r
+        });\r
+    }\r
+\r
+    newInstance() {\r
+        return new DefinitionInstance();\r
+    }\r
+\r
+\r
+\r
+\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.pug b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.pug
new file mode 100644 (file)
index 0000000..8807f2d
--- /dev/null
@@ -0,0 +1,78 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+form(#testHeadForm="ngForm", style="width:100%")\r
+  .row\r
+    .col-sm-6\r
+      mat-form-field(*ngIf="vth._id")\r
+        input(matInput, type="text", name="_id", placeholder="Test Head ID", [ngModel]='vth._id', disabled)\r
+\r
+      mat-form-field\r
+        input(matInput, type="text", name="test_head_name", placeholder="Name", [(ngModel)]="vth.testHeadName", required)\r
+\r
+      mat-form-field\r
+        input(matInput, type="text", name="test_head_hostname", placeholder="Hostname", [(ngModel)]="vth.hostname")\r
+\r
+      mat-form-field\r
+        input(matInput, type="text", name="test_head_urlPath", placeholder="Resource Path", [(ngModel)]="vth.resourcePath")\r
+\r
+    .col-sm-6\r
+      mat-form-field\r
+        input(matInput, name="description", placeholder="Description", [(ngModel)]="vth.testHeadDescription", required)\r
+\r
+      mat-form-field\r
+        input(matInput, type="text", name="test_head_port", placeholder="Port", [(ngModel)]="vth.port")\r
+\r
+      .row\r
+          .col-sm-4\r
+             mat-checkbox(name="test_head_authorization_enabled", (change)="markAsDirty()", [(ngModel)]="vth.authorizationEnabled") Authorization\r
+\r
+          .col-sm-3\r
+              mat-form-field\r
+                  input(matInput, type="text", name="test_head_authorization_type", placeholder="Type (ex: ApiKey)", [(ngModel)]="vth.authorizationType")\r
+\r
+          .col-sm-5\r
+              mat-form-field\r
+                  input(matInput, type="text", autocomplete="off", name="test_head_authorization_credential", placeholder="Password", [(ngModel)]="vth.authorizationCredential")\r
+\r
+\r
+\r
+          //- mat-form-field\r
+      //-   mat-select((selectionChange)="markAsDirty()", name="ns", placeholder="User Group", [(value)]="vth.groupId", required)\r
+      //-     mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}\r
+\r
+    .col-12\r
+      h5.text-muted vthInputTemplate.yaml\r
+      input( type="file", id="file", (change)="saveFileContents()")\r
+      div(style="border: 1px solid lightgrey; font-size: 16px !important")\r
+        codemirror([config]="codeConfig", [(ngModel)]='vth.vthInputTemplate', name="vthInputTemplate")\r
+\r
+  //- .row.mt-3\r
+  //-   .col\r
+  //-     h5.text-muted vthOutputTemplate.yaml\r
+  //-     div(style="border: 1px solid lightgrey; font-size: 16px !important")\r
+  //-       codemirror([config]="codeConfig", [(ngModel)]='vth.vthOutputTemplate', name="vthOutputTemplate")\r
+\r
+\r
+  .row(style="height:30px")\r
+  .row.form-buttons\r
+    .col-12\r
+      .pull-left\r
+        .mr-3(mat-button, *ngIf="testHeadForm.form.valid && !testHeadForm.form.dirty && options.goal == 'edit'") saved\r
+          mat-icon(style="color:green") check\r
+      .pull-right\r
+        button.mr-3(mat-raised-button, color="primary", (click)='create()', *ngIf="options.goal == 'create'", [disabled]="!testHeadForm.form.valid") Create\r
+        button.mr-3(mat-raised-button, color="accent", (click)='update()', *ngIf="options.goal == 'edit'", [disabled]="!testHeadForm.form.valid || !testHeadForm.form.dirty") Update\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.scss b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.scss
new file mode 100644 (file)
index 0000000..8d6ae72
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// .ng-valid[required], .ng-valid.required  {\r
+//     border-left: 3px solid #42A948; /* green */\r
+// }\r
+\r
+// .ng-invalid:not(form)  {\r
+//     border-left: 3px solid #a94442; /* red */\r
+// }\r
+\r
+// .ng-dirty:not(form) {\r
+//     border-left: 3px solid #045C87; /* blue */\r
+// }\r
+\r
+mat-form-field {\r
+    width: 100%;\r
+}\r
+\r
+.CodeMirror-scroll {\r
+    height: 200px\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.spec.ts b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.spec.ts
new file mode 100644 (file)
index 0000000..efb2b4a
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { CreateTestHeadFormComponent } from './create-test-head-form.component';\r
+\r
+describe('CreateTestHeadFormComponent', () => {\r
+  let component: CreateTestHeadFormComponent;\r
+  let fixture: ComponentFixture<CreateTestHeadFormComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ CreateTestHeadFormComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(CreateTestHeadFormComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.ts b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.ts
new file mode 100644 (file)
index 0000000..4e0f459
--- /dev/null
@@ -0,0 +1,170 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ListService } from '../../services/list.service';\r
+import { TestHeadService } from '../../services/test-head.service';\r
+import { GroupService } from '../../services/group.service';\r
+import 'codemirror/mode/yaml/yaml.js';\r
+import { MatSnackBar, MatDialog, MatDialogRef } from '@angular/material';\r
+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';\r
+import { AlertModalComponent } from '../alert-modal/alert-modal.component';\r
+\r
+\r
+@Component({\r
+  selector: 'app-create-test-head-form',\r
+  templateUrl: './create-test-head-form.component.pug',\r
+  styleUrls: ['./create-test-head-form.component.scss']\r
+})\r
+export class CreateTestHeadFormComponent implements OnInit {\r
+  yaml;\r
+\r
+  private hasPrevCredential;\r
+\r
+  public codeConfig = {\r
+    mode: "yaml",\r
+    theme: "eclipse",\r
+    lineNumbers: true\r
+  };\r
+\r
+  @Input() public formData;\r
+  @Input() public options;\r
+\r
+  @Output() public childEvent = new EventEmitter();\r
+\r
+  //Virtual Test Head Type Options\r
+  types = [\r
+    'Proxy',\r
+    'Regular',\r
+    'Script',\r
+    'Adapter'\r
+  ]\r
+\r
+  //Implementation Language Options\r
+  langs = [\r
+    'Java',\r
+    'Python',\r
+    'JavaScript/NodeJS'\r
+  ]\r
+\r
+  public vth;\r
+  public groups;\r
+\r
+  @ViewChild('testHeadForm') form: any;\r
+\r
+  constructor(public dialogRef: MatDialogRef<CreateTestHeadFormComponent>, private http: HttpClient, private list: ListService, private dialog: MatDialog, private snack: MatSnackBar, private testHead: TestHeadService, private group: GroupService) { }\r
+\r
+  ngOnInit() {\r
+    this.setNew();\r
+    if(this.formData){\r
+      this.vth = Object.assign({}, this.formData);\r
+      if(!this.vth.authorizationCredential){\r
+          this.vth.authorizationCredential = "";\r
+          this.hasPrevCredential = false;\r
+      }\r
+      else{\r
+          this.hasPrevCredential = true\r
+      }\r
+    }\r
+  }\r
+\r
+  markAsDirty(){\r
+    this.form.control.markAsDirty();\r
+  }\r
+\r
+  create(){\r
+\r
+    this.testHead.create(this.vth)\r
+    .subscribe((vth) => {\r
+      //this.list.addElement('vth', vth);\r
+      this.clear(this.form);\r
+      this.snack.openFromComponent(AlertSnackbarComponent, {\r
+        duration: 1500,\r
+        data: {\r
+          message:'Test Head Created'\r
+        }\r
+      });\r
+      this.dialogRef.close();\r
+      //this.dialog.closeAll();\r
+    }, err => {\r
+      this.dialog.open(AlertModalComponent, {\r
+        data: {\r
+          type: 'alert',\r
+          message: JSON.stringify(err)\r
+        },\r
+        width: '450px'\r
+      })\r
+    });\r
+\r
+  }\r
+  //grab file\r
+  saveFileContents(){\r
+    this.getFileContents(val => {\r
+      this.vth.vthInputTemplate = val;\r
+    });\r
+  }\r
+\r
+  getFileContents(callback) {\r
+    var val = "x";\r
+    var fileToLoad = (document.getElementById('file'))['files'][0];\r
+    var fileReader = new FileReader();\r
+    if (!fileToLoad) {\r
+      return null;\r
+    }\r
+    fileReader.onload = function (event) {\r
+      //\r
+      val = event.target['result'];\r
+\r
+      //\r
+      callback(val);\r
+    }\r
+    fileReader.readAsText(fileToLoad);\r
+  }\r
+\r
+  update(){\r
+    if(!this.hasPrevCredential && this.vth.authorizationCredential == ""){\r
+          delete this.vth.authorizationCredential;\r
+    }\r
+    this.testHead.patch(this.vth)\r
+    .subscribe((vth) => {\r
+      // this.list.updateElement('vth', '_id', vth['_id'], vth);\r
+      this.childEvent.emit();\r
+        this.snack.openFromComponent(AlertSnackbarComponent, {\r
+            duration: 1500,\r
+            data: {\r
+                message:'Test Head Updated'\r
+            }\r
+        });\r
+        this.dialogRef.close();\r
+    });\r
+  }\r
+\r
+  clear(form){\r
+    this.setNew();\r
+    if(form){\r
+      form.reset();\r
+    }\r
+    this.childEvent.emit();\r
+  }\r
+\r
+  setNew(){\r
+    this.vth = {};\r
+    this.vth.vthInputTemplate = '';\r
+\r
+    //this.vth.vthOutputTemplate = '';\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.spec.ts b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.spec.ts
new file mode 100644 (file)
index 0000000..b039050
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { CreateTestHeadFormModule } from './create-test-head-form.module';\r
+\r
+describe('CreateTestHeadFormModule', () => {\r
+  let createTestHeadFormModule: CreateTestHeadFormModule;\r
+\r
+  beforeEach(() => {\r
+    createTestHeadFormModule = new CreateTestHeadFormModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(createTestHeadFormModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.ts b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.ts
new file mode 100644 (file)
index 0000000..14b83e1
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { CreateTestHeadFormComponent } from './create-test-head-form.component';\r
+import { FormsModule } from '@angular/forms';\r
+import {\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatSnackBarModule,\r
+    MatIconModule,\r
+    MatDialogModule,\r
+    MatSlideToggleModule, MatCheckboxModule\r
+} from '@angular/material';\r
+import { CodemirrorModule } from 'ng2-codemirror';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    CodemirrorModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatSnackBarModule,\r
+    AlertSnackbarModule,\r
+    MatIconModule,\r
+    MatDialogModule,\r
+    MatSlideToggleModule,\r
+    MatCheckboxModule\r
+  ],\r
+  declarations: [CreateTestHeadFormComponent],\r
+  exports: [CreateTestHeadFormComponent ]\r
+})\r
+export class CreateTestHeadFormModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.pug b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.pug
new file mode 100644 (file)
index 0000000..8739065
--- /dev/null
@@ -0,0 +1,139 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.row(style="margin-left: 0px")\r
+  .col\r
+    button.mr-2.mt-2(mat-raised-button, color="primary", (click)="getDefinition()") Select Definition\r
+    label() Selected Test Defintion: {{ selectedDefinition.testName || "None Selected" }}\r
+    div.mt-4(*ngIf="selectedDefinition.testName")\r
+      .col-md-12.mb-4\r
+        .row\r
+          mat-form-field.mr-3\r
+            input(matInput, [(ngModel)]="testInstance.testInstanceName", placeholder="Instance Name", required)\r
+            mat-error Required\r
+          mat-form-field\r
+            input(matInput, [(ngModel)]="testInstance.testInstanceDescription", placeholder="Description") \r
+        .row\r
+          Label() Select BPMN Version\r
+        .row \r
+          .col-md-4\r
+            mat-select.mr-2([(value)]="selectedBpmn", [disabled]="testInstance.useLatestTestDefinition || editMode", (selectionChange)="changeBpmn()") \r
+              mat-option( *ngFor="let bpmn of selectedDefinition.bpmnInstances | filterNonDeployed: myFilter", [value]="bpmn") {{bpmn.version}}\r
+\r
+          .col-md-4\r
+            mat-slide-toggle(*ngIf='!editMode', [(ngModel)]="testInstance.useLatestTestDefinition", (change)="useLatest()") Use latest\r
+            mat-slide-toggle.ml-2(color="primary", [(ngModel)]="testInstance.simulationMode", (change)="simulationMode()") Simulation Mode\r
+          \r
+      mat-accordion\r
+        mat-expansion-panel([expanded]="false")\r
+          mat-slide-toggle((change)="toggleYaml()", [checked]="displayYAML") Display Yaml Input\r
+          mat-expansion-panel-header Test Input\r
+          div(*ngIf='testInstance.testDataJSON || selectedBpmn.testDataTemplateJSON')\r
+            app-form-generator(*ngIf="!displayYAML", [JSONData] = 'testInstance.testDataJSON || selectedBpmn.testDataTemplateJSON', [taskId]= '', (childEvent)="saveTestDataOptions($event)" )\r
+          codemirror(*ngIf="displayYAML", [config]="codeConfig", [(ngModel)] = "testInstance['testData']")\r
+      \r
+      //- If Not in simulation mode, display vth input fields\r
+      div(*ngIf="!testInstance.simulationMode")\r
+        mat-slide-toggle.mt-4.mb-2((change) = "testHeadYaml()") Display Yaml (All VTHs)\r
+        mat-accordion(*ngFor = 'let testHead of selectedBpmn.testHeads; let i = index')\r
+          mat-expansion-panel(*ngIf="editMode || (testHead.testHeadId.testHeadName && testHead.testHeadId.testHeadName.toLowerCase() != 'robot')",[expanded]='false')\r
+            mat-expansion-panel-header {{ testHead.testHeadId.testHeadName || testInstance.vthInput[testHead.bpmnVthTaskId + "testHeadName"] }} ({{testHead.bpmnVthTaskId}})\r
+            app-form-generator(*ngIf= "!testHeadYAML", [JSONData] = 'testInstance.vthInput[testHead.bpmnVthTaskId] || testHead["testHeadId"]["vthInputTemplateJSON"]', [taskId]="testHead.bpmnVthTaskId",  (childEvent)="saveFormOptions($event)")\r
+\r
+            codemirror(*ngIf="testHeadYAML", [config]="codeConfig", [(ngModel)] = "testInstance['vthInputYaml'][testHead.bpmnVthTaskId]")\r
+      \r
+          mat-expansion-panel(*ngIf="(testHead.testHeadId.testHeadName && testHead.testHeadId.testHeadName.toLowerCase() == 'robot')", [expanded]='false')\r
+            mat-expansion-panel-header {{ testHead.testHeadId.testHeadName || testInstance.vthInput[testHead.bpmnVthTaskId + "testHeadName"]}} ({{testHead.bpmnVthTaskId}}) Robot Files\r
+            mat-panel-title Resources\r
+            .row\r
+              .col-md-3\r
+                //- .mb-2 TESTING GIT TRACKING\r
+                //-   | Multiple Files \r
+                //-   mat-slide-toggle(color="primary", name="isZip", [(ngModel)]="isZip", (change)="uploader.clearQueue()")\r
+                //-   |  .zip\r
+                //- div \r
+                //-   input(*ngIf="!isZip", type="file", name="scripts", ng2der")FileSelect, [uploader]="uploader", multiple)\r
+                input(*ngIf="isZip", type="file", name="scripts", ng2FileSelect, [uploader]="uploaders[testHead.bpmnVthTaskId]", accept="application/zip")\r
+              .col-md-8.ml-2\r
+                div(*ngIf="uploaders[testHead.bpmnVthTaskId].queue.length > 0")\r
+                  label Files:\r
+                  ul.list-group(style="position:relative")\r
+                    li.list-group-item(*ngFor="let item of uploaders[testHead.bpmnVthTaskId].queue")\r
+                      | {{ item?.file?.name }}\r
+                      div.upload-progress([ngStyle]="{'width': item.progress + '%'}")\r
+                  //button.pull-right(mat-button, (click)="upload()") Upload All\r
+                  button.pull-right(mat-button, color="primary", (click)="uploaders[testHead.bpmnVthTaskId].clearQueue()") Remove All\r
+      \r
+      //- If in simulation mode, show simulated outputs and delays\r
+      div.mt-4(*ngIf="testInstance.simulationMode && testInstance.simulationVthInput")\r
+        mat-accordion\r
+          mat-expansion-panel(*ngFor="let testHead of selectedBpmn.testHeads; let i = index")\r
+            mat-expansion-panel-header \r
+              span(style="color: #2196f3") Simulated \r
+              | &nbsp; {{ testHead.testHeadId.testHeadName || testInstance.vthInput[testHead.bpmnVthTaskId + "testHeadName"] }} ({{testHead.bpmnVthTaskId}})\r
+            codemirror([config]="codeJsonConfig", *ngIf="testInstance.simulationVthInput[testHead.bpmnVthTaskId]", [(ngModel)]="testInstance.simulationVthInput[testHead.bpmnVthTaskId]")\r
+          //- h5.text-muted testHeadData.yaml\r
+          //-  div(style="border: 1px solid lightgrey")\r
+          //-   codemirror([config]="codeConfig", value = "{{ testInstance['testData']}}", [(ngModel)]='testInstance["testData"]')\r
+      div.mt-4(*ngIf="checkPfloInputLength()")\r
+        h4 PFLO Inputs\r
+        mat-accordion\r
+          mat-expansion-panel(*ngFor="let pflo of selectedBpmn.pflos; let i = index" color="primary")\r
+            mat-expansion-panel-header {{testInstance.pfloInput[pflo.bpmnPfloTaskId + "pfloName"]}} ({{pflo.bpmnPfloTaskId}})\r
+            .row\r
+              .col-md-6()\r
+                h5 Stop on Failure\r
+                mat-form-field\r
+                  mat-select(placeholder="Interrupt On Failure", [(value)]="testInstance.pfloInput[pflo.bpmnPfloTaskId]['interruptOnFailure']", required)\r
+                    mat-option([value]="false") False\r
+                    mat-option([value]="true") True\r
+                h5 Max Number of Failures\r
+                mat-form-field\r
+                  input(matInput, type="number", [(ngModel)] = "testInstance.pfloInput[pflo.bpmnPfloTaskId]['maxFailures']")\r
+              .col-md-6\r
+                h5 Number of Threads\r
+                mat-form-field\r
+                  input(matInput, type="number", [(ngModel)] = "testInstance.pfloInput[pflo.bpmnPfloTaskId]['threadPoolSize']")\r
+                \r
+                .dropdown.mt-1(ngbDropdown, autoClose="outside", (openChange)="clearSelectedValues()", placement="left-top")\r
+                  button(mat-raised-button, [disabled]="editMode",  color="primary", ngbDropdownToggle, (click)="null") Add Instance\r
+                    i.ml-1.fa.fa-caret-down\r
+                  .dropdown-menu(ngbDropdownMenu)\r
+                    h4.mb-2.ml-1(style="font-weight: bold;") Add Instances\r
+                    input.ml-1(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.testInstanceName')\r
+                    div(style="max-height: 300px; overflow-y: scroll")\r
+                      .px-4.py-3\r
+                        .mr-2.ml-2(*ngFor="let instance of instances | filterBy:search")\r
+                          mat-checkbox([(ngModel)]='instance.isSelected') {{instance.testInstanceName}} \r
+                    div( style="text-align: center")            \r
+                      button.primary.mr-1(mat-raised-button, aria-label='Add', color="primary", (click)='addInstancesToPflo(pflo.bpmnPfloTaskId)') Add\r
+                  \r
+              h4.mt-2(*ngIf="testInstance.pfloInput[pflo.bpmnPfloTaskId].args.length && !editMode", style="width:100%") Workflows\r
+                mat-accordion\r
+                  mat-expansion-panel(*ngFor="let workReq of testInstance.pfloInput[pflo.bpmnPfloTaskId].args; let i = index")\r
+                    mat-expansion-panel-header(style="align-text:center") {{tiNameLookup[workReq.testInstanceId]}}\r
+                      //button.primary.mr-1.ml-4(mat-mini-fab, aria-label='Remove', color="warn", (click)="deleteWorkReq(pflo.bpmnPfloTaskId, i)")\r
+                      i.fa.fa-remove.ml-2((click)="deleteWorkReq(pflo.bpmnPfloTaskId, i)")\r
+                    app-workflow-request([formData]='testInstance.pfloInput[pflo.bpmnPfloTaskId].args[i]', [taskId]="pflo.bpmnPfloTaskId", [index]="i",  (childEvent)="saveWorkReqForm($event)")\r
+.row(style="height:40px")\r
+.row.form-buttons(*ngIf = "selectedDefinition.testName")\r
+  .col-12.mt-3\r
+    .pull-right\r
+      h5.mr-2(style="color: Red", *ngIf='executionFailed') Tests failed to execute! \r
+      button.mr-2(mat-raised-button, *ngIf='!editMode', color="primary", (click)="saveAll()") Save\r
+      button(mat-raised-button, *ngIf='!editMode', color="warn", (click)="saveAndExecute()") Save and Execute\r
+      button.mr-2(mat-raised-button, *ngIf='editMode', color="primary", (click)="updateInstance()") Update\r
+      button.mr-2(mat-raised-button, *ngIf='editMode', color="primary", (click)="cancel()") Cancel
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.scss b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.scss
new file mode 100644 (file)
index 0000000..d93cec9
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.dropdown-toggle::after {\r
+    display:none;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.spec.ts b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.spec.ts
new file mode 100644 (file)
index 0000000..16c79dd
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { CreateTestInstanceFormComponent } from './create-test-instance-form.component';\r
+\r
+describe('CreateTestInstanceFormComponent', () => {\r
+  let component: CreateTestInstanceFormComponent;\r
+  let fixture: ComponentFixture<CreateTestInstanceFormComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ CreateTestInstanceFormComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(CreateTestInstanceFormComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.ts b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.ts
new file mode 100644 (file)
index 0000000..df703b4
--- /dev/null
@@ -0,0 +1,788 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';\r
+import 'codemirror/mode/yaml/yaml.js';\r
+import { TestInstanceService } from '../../services/test-instance.service';\r
+import { TestDefinitionService } from '../../services/test-definition.service';\r
+import { SchedulingService } from '../../services/scheduling.service';\r
+import { SelectStrategyModalComponent } from '../select-strategy-modal/select-strategy-modal.component';\r
+import { MatDialog, MatSnackBar } from '@angular/material';\r
+import { AlertModalComponent } from '../alert-modal/alert-modal.component';\r
+import { Router } from '@angular/router';\r
+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';\r
+import { ListService } from 'app/shared/services/list.service';\r
+import { FileUploader, FileItem, ParsedResponseHeaders } from 'ng2-file-upload';\r
+import { HttpClient, HttpHeaders } from "@angular/common/http";\r
+import { AppGlobals } from "../../../app.global";\r
+import { CookieService } from "ngx-cookie-service";\r
+import * as  YAML from '../../../../../../node_modules/yamljs/lib/Yaml';\r
+import 'codemirror/mode/javascript/javascript.js';\r
+import beautify from 'json-beautify';\r
+import { WorkflowRequest } from './instance.class';\r
+import { PfloInputClass } from './instance.class';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+import { ExecuteService } from 'app/shared/services/execute.service';\r
+\r
+const URL = AppGlobals.baseAPIUrl + 'files';\r
+\r
+\r
+@Component({\r
+  selector: 'app-create-test-instance-form',\r
+  templateUrl: './create-test-instance-form.component.pug',\r
+  styleUrls: ['./create-test-instance-form.component.scss']\r
+})\r
+export class CreateTestInstanceFormComponent implements OnInit {\r
+  yaml;\r
+  //Variable sent between modules\r
+  @Input() public existingInstance: any;\r
+\r
+  @Output() public childEvent = new EventEmitter();\r
+  public dataTemplate: any;\r
+  public configTemplate: any;\r
+\r
+  public codeConfig = {\r
+    mode: "yaml",\r
+    theme: "eclipse",\r
+    lineNumbers: true\r
+  };\r
+\r
+  public codeJsonConfig = {\r
+    mode: "application/json",\r
+    theme: "eclipse",\r
+    lineNumbers: true\r
+  }\r
+\r
+  public testDefinition;\r
+  public testInstance;\r
+  public createResult;\r
+  public selectedDefinition;\r
+  public errorCount = 0;\r
+  public executionFailed = false;\r
+  public editMode = false;\r
+  public httpOptions;\r
+  public selectedBpmn;\r
+  public uploader: FileUploader;\r
+  public isZip = true;\r
+  public scriptFiles = [];\r
+  public uploaders = {};\r
+  public vthInput = {};\r
+  public pfloInput = {};\r
+  public argsToAdd = {};\r
+  public vthInputYaml = {};\r
+  public displayYAML = false;\r
+  public testHeadYAML = false;\r
+  public testHeadNames = {};\r
+  public tiNameLookup = {};\r
+  public instances;\r
+  public search;\r
+  public instanceAdded;\r
+  \r
+\r
+  public uploadOptions = {\r
+    url: AppGlobals.baseAPIUrl + 'file-transfer',\r
+    authTokenHeader: 'Authorization',\r
+    authToken: 'Bearer ' + JSON.parse(this.cookie.get('access_token'))\r
+  };\r
+\r
+  // , private http: HttpClient, private Params: ParamsService, private cookie: CookieService\r
+  constructor(private router: Router, private list: ListService, private dialog: MatDialog, private execute: ExecuteService, private testInstanceService: TestInstanceService, private testDefinitionService: TestDefinitionService, private snack: MatSnackBar, private http: HttpClient, private cookie: CookieService, private groupService: GroupService) {\r
+    this.http = http;\r
+    this.cookie = cookie;\r
+    // this.httpOptions = {\r
+    //     headers: new HttpHeaders({ \r
+    //       'Content-Type': 'application/json',\r
+    //       'Authorization': 'Bearer ' + JSON.parse(this.cookie.get('access_token'))\r
+    //     })\r
+    //   };\r
+  }\r
+  // testingSelect(){\r
+  //   console.log(this.selectedBpmn);\r
+  // }\r
+  myFilter(bpmn) {\r
+    return bpmn.isDeployed;\r
+  }\r
+  ngOnInit() {\r
+    this.search = {};\r
+    this.search.testInstanceName = '';\r
+    this.testInstance = {};\r
+    this.selectedDefinition = {};\r
+    this.selectedBpmn = {};\r
+    this.testInstance.useLatestTestDefinition = true;\r
+    this.testInstance.simulationVthInput = {};\r
+    let currentGroup;\r
+    //options required for the file uploader\r
+    currentGroup = this.groupService.getGroup();\r
+    this.groupService.groupChange().subscribe(group => {\r
+      currentGroup = group;\r
+    });\r
+    \r
+    this.testInstanceService.find({\r
+      groupId: currentGroup['_id'],\r
+      $limit: -1,\r
+      $sort: {\r
+        createdAt: -1,\r
+      },\r
+      $select: ['testInstanceName']\r
+    }).subscribe((result) => {\r
+      this.instances = result;\r
+      for(let i = 0; i < this.instances.length; i++){\r
+        this.instances[i].isSelected = false;\r
+      }\r
+    })\r
+\r
+    //File Uploaders\r
+    //this.uploader = new FileUploader(uploadOptions);\r
+    //if the user is using this page for editing an existing instance\r
+    if (this.existingInstance) {\r
+      //console.log(this.existingInstance)\r
+      if (this.existingInstance.testInstance) {\r
+        this.testInstance = this.existingInstance.testInstance;\r
+        this.selectedDefinition = this.existingInstance.testInstance['testDefinitionId'];\r
+        \r
+        this.convertSimulationVth('string');\r
+        console.log(this.testInstance);\r
+\r
+        //set the bpmn to the selected bpmn. Alert User if no bpmn versions are deployed\r
+        if (this.testInstance.useLatestTestDefinition) {\r
+          this.useLatest();\r
+        } else {\r
+          for (let i = 0; i < this.selectedDefinition.bpmnInstances.length; i++) {\r
+            if (this.selectedDefinition.bpmnInstances[i].processDefintionId === this.testInstance.processDefintionId) {\r
+              this.selectedBpmn = this.selectedDefinition.bpmnInstances[i];\r
+              break;\r
+            }\r
+          }\r
+        }\r
+\r
+        if (this.testInstance.testData === '') {\r
+          this.displayYAML = true;\r
+        }\r
+\r
+        if (!this.testInstance.simulationVthInput) {\r
+          this.testInstance.simulationVthInput = {};\r
+        }\r
+\r
+\r
+        //grab all robot test heads to assign uploaders to each and create the vthInput object\r
+        //for(let j = 0; j < this.selectedBpmn.testHeads.length; j++){\r
+\r
+\r
+        //}\r
+        //console.log(this.uploaders);\r
+        if (this.existingInstance.isEdit == true)\r
+          this.editMode = true;\r
+      }//if the user is creating a new instance from the test definition page\r
+      else if (this.existingInstance.testDefinition) {\r
+        this.selectedDefinition = this.existingInstance.testDefinition;\r
+        this.populateTIName();\r
+        //set the bpmn as the latest deployed version. Alert User if no bpmn versions are deployed\r
+        this.useLatest();\r
+        this.populateVthInput();\r
+        this.populatePfloInput();\r
+        //grab all robot test heads to assign uploaders to each and set the vthInput object\r
+        for (let j = 0; j < this.selectedBpmn.testHeads.length; j++) {\r
+\r
+          if (this.selectedBpmn.testHeads[j].testHeadId['testHeadName'].toLowerCase() === 'robot') {\r
+            this.uploaders[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = new FileUploader(this.uploadOptions);\r
+          }\r
+        }\r
+\r
+        this.testInstance = {\r
+          "testInstanceDescription": "",\r
+          "testDefinitionId" : this.selectedDefinition["_id"],\r
+          "vthInput" : this.vthInput,\r
+          "pfloInput": this.pfloInput,\r
+          "vthInputYaml": this.vthInputYaml,\r
+          "testData": this.selectedBpmn.testDataTemplate,\r
+          "testDataJSON": this.selectedBpmn.testDataTemplateJSON,\r
+          "useLatestTestDefinition": true,\r
+          "internalTestData": {},\r
+          "simulationVthInput": {}\r
+        };\r
+\r
+      }\r
+    }\r
+    \r
+  }\r
+\r
+  convertSimulationVth(convertTo) {\r
+    for (let key in this.testInstance.simulationVthInput) {\r
+      if (this.testInstance.simulationVthInput.hasOwnProperty(key)) {\r
+        if(convertTo == 'json')\r
+          this.testInstance.simulationVthInput[key] = JSON.parse(this.testInstance.simulationVthInput[key]);\r
+        else if (convertTo == 'string')\r
+          this.testInstance.simulationVthInput[key] = beautify(this.testInstance.simulationVthInput[key], null, 2, 10);\r
+      }\r
+    }\r
+\r
+  }\r
\r
+\r
+\r
+  simulationMode() {\r
+    let def = {\r
+      delay: 0, response: {}\r
+    };\r
+    //console.log(this.selectedBpmn);\r
+    if (this.testInstance.simulationMode) {\r
+      this.selectedBpmn.testHeads.forEach(e => {\r
+        if(!this.testInstance.simulationVthInput){\r
+          this.testInstance.simulationVthInput = {}\r
+        }\r
+        if (!this.testInstance.simulationVthInput[e.bpmnVthTaskId]) {\r
+          this.testInstance.simulationVthInput[e.bpmnVthTaskId] = beautify(def, null, 2, 10);\r
+        }\r
+      })\r
+    }\r
+  }\r
+\r
+  populateTIName() {\r
+    let list;\r
+    this.testInstanceService.find({ $limit: -1, $select: ['testInstanceName'], testDefinitionId: this.selectedDefinition._id }).subscribe((res) => {\r
+      list = res;\r
+      //console.log(list);\r
+      let num = list.length;\r
+      if (num === 0) {\r
+        this.testInstance.testInstanceName = this.selectedDefinition.testName;\r
+      } else {\r
+        this.testInstance.testInstanceName = this.selectedDefinition.testName + num;\r
+      }\r
+      let isTaken = true;\r
+      let count = 0;\r
+      let alreadyExisted = false;\r
+      while (isTaken === true && count < 10000) {\r
+        for (let i = 0; i < list.length; i++) {\r
+          if (list[i]["testInstanceName"] === this.testInstance.testInstanceName) {\r
+            num++;\r
+            this.testInstance.testInstanceName = this.selectedDefinition.testName + num;\r
+            alreadyExisted = true;\r
+            break;\r
+          }\r
+        }\r
+        if (alreadyExisted) {\r
+          alreadyExisted = false;\r
+        } else {\r
+          isTaken = false;\r
+        }\r
+        count++;\r
+      }\r
+    });\r
+  }\r
+  //Section for implementing Paralell workflow data entry --------------------------------------------------------------------------------------\r
+  populatePfloInput(){\r
+    // this.pfloInput = {\r
+    //   "task123": new PfloInputClass\r
+    // }\r
+    //this.selectedBpmn.pflos = [{"bpmnPfloTaskId" : "task123", "label": "TestPFLO"}]\r
+    \r
+    if(this.testInstance.pfloInput){  \r
+      return;\r
+    }\r
+\r
+    this.pfloInput = {};\r
+   \r
+    if(this.selectedBpmn == {} || !this.selectedBpmn.pflos){\r
+      \r
+      this.testInstance.pfloInput = this.pfloInput;\r
+      return;\r
+    }\r
+\r
+    for(let i = 0; i < this.selectedBpmn.pflos.length; i++){\r
+      if(this.selectedBpmn.pflos[i]['bpmnPfloTaskId'] != null){\r
+        this.pfloInput[this.selectedBpmn.pflos[i]['bpmnPfloTaskId']] = new PfloInputClass;\r
+       \r
+        //this.pfloInput[this.selectedBpmn.pflos[i]['bpmnPfloTaskId'] + "pfloName"] = this.selectedBpmn.pflos[i]['label'];\r
+      }\r
+    }\r
+    this.testInstance.pfloInput = this.pfloInput;\r
+    \r
+  }\r
+\r
+  \r
+  addInstancesToPflo(taskId){\r
+    for(let i = 0; i < this.instances.length; i++){\r
+      if(this.instances[i].isSelected){\r
+        this.tiNameLookup[this.instances[i]._id] = this.instances[i].testInstanceName;\r
+        this.addPfloInput(taskId, this.instances[i]._id);\r
+      }\r
+\r
+    }\r
+  }\r
+\r
+  addPfloInput(taskId, instanceId){\r
+     \r
+    this.testInstance.pfloInput[taskId].args.push(new WorkflowRequest(instanceId));\r
+   \r
+  }\r
+\r
+  clearSelectedValues(){\r
+    this.search.testInstanceName = '';\r
+    for(let i = 0; i < this.instances.length; i++){\r
+      this.instances[i].isSelected = false;\r
+    }\r
+  }\r
+\r
+  saveTestDataOptions(event) {\r
+    this.testInstance.testData = event.object;\r
+  }\r
+\r
+  saveFormOptions(event) {\r
+    this.testInstance.vthInput[event.taskId] = event.object;\r
+    //console.log(this.testInstance);\r
+  }\r
+\r
+  \r
+  checkPfloInputLength(){\r
+  \r
+    if(this.testInstance.pfloInput != null){\r
+      let temp =  Object.keys(this.testInstance.pfloInput);\r
+      if(temp.length)\r
+        return temp.length > 0;\r
+      else\r
+        return false;\r
+    }else{\r
+      return false;\r
+    }\r
+  }\r
+\r
+  deleteWorkReq(pfloId, index){\r
+    this.testInstance.pfloInput[pfloId].args.splice(index, 1);\r
+    //FORCE REFRESH all connected forms to update their index\r
+  }\r
+\r
+  saveWorkReqForm(event) {\r
+    this.testInstance.pfloInput[event.taskId].args[event.index] = event.object;\r
+    //console.log(this.testInstance);\r
+  }\r
+\r
+  convertTestLevelYaml() {\r
+    if (this.displayYAML) {\r
+      this.testInstance.testDataJSON = YAML.parse(this.testInstance.testData);\r
+    } else {\r
+      this.testInstance.testData = YAML.stringify(this.testInstance.testDataJSON);\r
+    }\r
+  }\r
+\r
+  convertVTHYaml() {\r
+    if (this.testHeadYAML) {\r
+      for (let key in this.testInstance.vthInputYaml) {\r
+        this.testInstance.vthInput[key] = YAML.parse(this.testInstance.vthInputYaml[key]);\r
+      }\r
+    } else {\r
+\r
+      for (let key in this.testInstance.vthInput) {\r
+        this.testInstance.vthInputYaml[key] = YAML.stringify(this.testInstance.vthInput[key]);\r
+      }\r
+    }\r
+  }\r
+  //End of Paralell workflow data entry section --------------------------------------------------------------------------------------\r
+\r
+  changeBpmn() {\r
+    //populate the vth inputs when bpmn changes\r
+    this.populateVthInput();\r
+    this.displayYAML = !this.displayYAML;\r
+    this.testInstance.testDataJSON = this.selectedBpmn.testDataTemplateJSON;\r
+    this.testInstance.testData = this.selectedBpmn.testDataTemplate;\r
+    this.convertTestLevelYaml();\r
+    setTimeout(() => {\r
+      this.displayYAML = !this.displayYAML;\r
+    }, 200);\r
+\r
+  }\r
+  //toggle Yaml for test level data\r
+  toggleYaml() {\r
+    this.convertTestLevelYaml();\r
+    this.displayYAML = !this.displayYAML;\r
+  }\r
+  //toggles Yaml for testHeads\r
+  testHeadYaml() {\r
+    this.convertVTHYaml();\r
+    this.testHeadYAML = !this.testHeadYAML;\r
+  }\r
+  //onChange method for the use latest TD toggle\r
+  useLatest() {\r
+    if (this.testInstance.useLatestTestDefinition) {\r
+      let temp;\r
+      let orderNum;\r
+      let processKey;\r
+      for (let i = 0; i < this.selectedDefinition.bpmnInstances.length; i++) {\r
+        if (temp) {\r
+          processKey = this.selectedDefinition.bpmnInstances[i].processDefinitionId\r
+          if(processKey){\r
+            orderNum = processKey.split(":");\r
+            orderNum = orderNum[1];\r
+            //console.log("bpmn check : " + orderNum + " bpmn current: " + temp.processDefinitionId.split(':')[1]);\r
+            if (this.selectedDefinition.bpmnInstances[i].isDeployed && parseInt(orderNum) > parseInt(temp.processDefinitionId.split(':')[1])) {\r
+              temp = this.selectedDefinition.bpmnInstances[i];\r
+            }\r
+          }\r
+        } else {\r
+          if (this.selectedDefinition.bpmnInstances[i].isDeployed) {\r
+            temp = this.selectedDefinition.bpmnInstances[i];\r
+          }\r
+        }\r
+\r
+      }\r
+      if (temp.isDeployed) {\r
+        this.selectedBpmn = temp;\r
+      } else {\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '450px',\r
+          data: {\r
+            type: 'alert',\r
+            message: 'Test Definition does not contain a deployed bpmn instance. Please return to the Test Definition page and deploy.'\r
+          }\r
+        });\r
+        this.testInstance.useLatestTestDefinition = false;\r
+      }\r
+      this.populateVthInput();\r
+    }\r
+    this.populatePfloInput();\r
+  }\r
+\r
\r
+  //checks if the test instance has a required Name\r
+  allNamed() {\r
+    if (!this.testInstance.testInstanceName) {\r
+      return false;\r
+    }\r
+\r
+    return true;\r
+  }\r
+  \r
+  //populate the vthInputYaml for newly created testInstances\r
+  populateVthInput() {\r
+    this.vthInputYaml = {};\r
+    this.vthInput = {};\r
+    for (let i = 0; i < this.selectedBpmn.testHeads.length; i++) {\r
+      this.vthInputYaml[this.selectedBpmn.testHeads[i].bpmnVthTaskId] = this.selectedBpmn.testHeads[i].testHeadId.vthInputTemplate;\r
+      this.vthInputYaml[this.selectedBpmn.testHeads[i].bpmnVthTaskId + "testHeadName"] = this.selectedBpmn.testHeads[i].testHeadId.testHeadName;\r
+      if (this.selectedBpmn.testHeads[i].testHeadId.vthInputTemplateJSON) {\r
+        this.vthInput[this.selectedBpmn.testHeads[i].bpmnVthTaskId] = this.selectedBpmn.testHeads[i].testHeadId.vthInputTemplateJSON;\r
+        this.vthInput[this.selectedBpmn.testHeads[i].bpmnVthTaskId + "testHeadName"] = this.selectedBpmn.testHeads[i].testHeadId.testHeadName;\r
+      }\r
+\r
+    }\r
+\r
+  }\r
+  //Used to grab all test definitions for the user to select.\r
+  getDefinition() {\r
+    const dialogRef = this.dialog.open(SelectStrategyModalComponent, {\r
+      width: '450px',\r
+      data: {}\r
+    });\r
+\r
+    dialogRef.afterClosed().subscribe(result => {\r
+      //If the user already had a selected definition and selected a new one, prompt the user to be sure of change\r
+      if (result != '' && this.selectedDefinition.testName) {\r
+        this.dialog.open(AlertModalComponent, {\r
+          width: '450px',\r
+          data: {\r
+            type: 'confirmation',\r
+            message: 'Changing the Test Definition will erase the Instance you are currently writing.'\r
+          }\r
+        }).afterClosed().subscribe(response => {\r
+          if (response) {\r
+            this.selectedDefinition = result;            \r
+            this.populateTIName();\r
+            //set the bpmn as the latest deployed version. Alert User if no bpmn versions are deployed\r
+            this.useLatest();\r
+            this.populateVthInput();\r
+            this.populatePfloInput();\r
+            //grab all robot test heads to assign uploaders to each and initialize vthInput\r
+            for (let j = 0; j < this.selectedBpmn.testHeads.length; j++) {\r
+              if (this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON']) {\r
+                this.vthInput[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON'];\r
+              }\r
+\r
+              if (this.selectedBpmn.testHeads[j].testHeadId['testHeadName'].toLowerCase() === 'robot') {\r
+                this.uploaders[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = new FileUploader(this.uploadOptions);\r
+              }\r
+            }\r
+\r
+            this.testInstance = {\r
+              "testInstanceDescription": "",\r
+              "groupId": this.selectedDefinition["groupId"],\r
+              "testDefinitionId": this.selectedDefinition["_id"],\r
+              "vthInput": this.vthInput,\r
+              "pfloInput": this.pfloInput,\r
+              "vthInputYaml": this.vthInputYaml,\r
+              "testData": this.selectedBpmn.testDataTemplate,\r
+              "testDataJSON": this.selectedBpmn.testDataTemplateJSON,\r
+              "useLatestTestDefinition": true,\r
+              "internalTestData": {},\r
+              "simulationVthInput": {}\r
+\r
+            };\r
+          }\r
+        });\r
+\r
+        //else they did not have a test definition currently selected\r
+      } else {\r
+        this.selectedDefinition = result;\r
+        this.populateTIName();\r
+        //set the bpmn as the latest deployed version. Alert User if no bpmn versions are deployed\r
+        this.useLatest();\r
+        this.populateVthInput();\r
+        this.populatePfloInput();\r
+        //grab all robot test heads to assign uploaders to each\r
+        for (let j = 0; j < this.selectedBpmn.testHeads.length; j++) {\r
+          if (this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON']) {\r
+            this.vthInput[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON'];\r
+          }\r
+          if (this.selectedBpmn.testHeads[j].testHeadId['testHeadName'].toLowerCase() === 'robot') {\r
+            this.uploaders[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = new FileUploader(this.uploadOptions);\r
+          }\r
+        }\r
+             \r
+          \r
+         \r
+          this.testInstance = {\r
+            "testInstanceDescription": "",\r
+              "groupId": this.selectedDefinition["groupId"],\r
+              "testDefinitionId": this.selectedDefinition["_id"],\r
+              "vthInput": this.vthInput,\r
+              "pfloInput": this.pfloInput,\r
+              "vthInputYaml": this.vthInputYaml,\r
+              "testData": this.selectedBpmn.testDataTemplate,\r
+              "testDataJSON": this.selectedBpmn.testDataTemplateJSON,\r
+              "useLatestTestDefinition": true,\r
+              "internalTestData": {},\r
+              "simulationVthInput": {}\r
+          };\r
+          \r
+        }\r
+    });\r
+  }\r
+\r
+  //Saves the Test Instance Object to the database \r
+  saveAll() {\r
+    if (!this.allNamed()) {\r
+      this.dialog.open(AlertModalComponent, {\r
+        width: '450px',\r
+        data: {\r
+          type: 'alert',\r
+          message: 'The Instance is not named! Please ensure the Instance are named.'\r
+        }\r
+      }).afterClosed().subscribe((result) => {\r
+        return;\r
+      });\r
+    } else {\r
+\r
+      if (!this.testInstance.processDefinitionId) {\r
+        this.testInstance.processDefinitionId = this.selectedBpmn.processDefinitionId;\r
+      }\r
+      this.errorCount = 0;\r
+      if (!this.displayYAML) {\r
+        this.testInstance.testData = this.testInstance.testDataJSON;\r
+      }\r
+      if (this.testHeadYAML) {\r
+        this.testInstance.vthInput = this.testInstance.vthInputYaml;\r
+      }\r
+\r
+      this.convertSimulationVth('json');\r
+\r
+      this.testInstanceService.create(this.testInstance)\r
+        .subscribe(\r
+          (result) => {\r
+            if (Object.keys(this.uploaders).length > 0)\r
+              this.uploadFiles(result);\r
+\r
+            this.snack.openFromComponent(AlertSnackbarComponent, {\r
+              duration: 1500,\r
+              data: {\r
+                message: 'Test Instance Saved'\r
+              }\r
+            });\r
+            this.dialog.closeAll();\r
+          },\r
+          (error) => {\r
+            this.dialog.open(AlertModalComponent, {\r
+              width: '450px',\r
+              data: {\r
+                type: 'Alert',\r
+                message: error\r
+              }\r
+            });\r
+\r
+          });\r
+    }\r
+  }\r
+\r
+  updateInstance() {\r
+\r
+\r
+    if (!this.testInstance.processDefinitionId) {\r
+      this.testInstance.processDefinitionId = this.selectedBpmn.processDefinitionId;\r
+    }\r
+    this.errorCount = 0;\r
+    if (!this.displayYAML) {\r
+      this.testInstance.testData = this.testInstance.testDataJSON;\r
+    }\r
+    if (this.testHeadYAML) {\r
+      this.testInstance.vthInput = this.testInstance.vthInputYaml;\r
+    }\r
+\r
+    this.convertSimulationVth('json');\r
+\r
+    this.testInstanceService.update(this.testInstance)\r
+      .subscribe((result) => {\r
+        this.snack.openFromComponent(AlertSnackbarComponent, {\r
+          duration: 1500,\r
+          data: {\r
+            message: 'Test Instance Updated'\r
+          }\r
+        });\r
+        this.childEvent.emit();\r
+      });\r
+  }\r
+\r
+  cancel() {\r
+    this.childEvent.emit();\r
+  }\r
+  uploadFiles(result) {\r
+    for (let i = 0; i < this.selectedBpmn.testHeads.length; i++) {\r
+      if (!this.uploaders[this.selectedBpmn.testHeads[i].bpmnVthTaskId]) {\r
+        continue;\r
+      }\r
+      let key = this.selectedBpmn.testHeads[i].bpmnVthTaskId;\r
+      let uploader = this.uploaders[key];\r
+      if (uploader.queue.length > 0) {\r
+        uploader.uploadAll();\r
+        uploader.onCompleteItem = (item: FileItem, response: string, status: Number, headers: ParsedResponseHeaders) => {\r
+          this.scriptFiles.push(JSON.parse(response)[0]);\r
+        }\r
+      }\r
+      uploader.onCompleteAll = () => {\r
+\r
+        let scriptFilesId = [];\r
+        for (let i = 0; i < this.scriptFiles.length; i++) {\r
+          scriptFilesId.push(this.scriptFiles[i]['_id']);\r
+        }\r
+       \r
+        for (let i = 0; i < this.selectedBpmn.testHeads.length; i++) {\r
+          if (this.selectedBpmn.testHeads[i].testHeadId['testHeadName'].toLowerCase() === 'robot') {\r
+            this.testInstance.internalTestData[this.selectedBpmn.testHeads[i].bpmnVthTaskId] =\r
+              {\r
+                "robotFileId": scriptFilesId[0]\r
+              };\r
+          }\r
+        }\r
+        let ti = {\r
+          '_id': result._id,\r
+          'internalTestData': this.testInstance.internalTestData\r
+        }\r
+\r
+        this.testInstanceService.patch(ti).subscribe(\r
+          res => {\r
+            //console.log(res);\r
+            // resolve(res);\r
+          },\r
+          err => {\r
+            // console.log(err);\r
+            // reject(err);\r
+          }\r
+        );\r
+      }\r
+    }\r
+  }\r
+  //saves instance to the database and executes the test using the agenda scheduler\r
+  saveAndExecute() {\r
+    if (!this.allNamed()) {\r
+      this.dialog.open(AlertModalComponent, {\r
+        width: '450px',\r
+        data: {\r
+          type: 'alert',\r
+          message: 'One or more Instance is not named! Please ensure all Instances are named.'\r
+        }\r
+      }).afterClosed().subscribe((result) => {\r
+        return;\r
+      });\r
+    } else {\r
+\r
+      if (!this.testInstance.processDefinitionId) {\r
+        this.testInstance.processDefinitionId = this.selectedBpmn.processDefinitionId;\r
+      }\r
+      this.errorCount = 0;\r
+      if (!this.displayYAML) {\r
+        this.testInstance.testData = this.testInstance.testDataJSON;\r
+      }\r
+      if (this.testHeadYAML) {\r
+        this.testInstance.vthInput = this.testInstance.vthInputYaml;\r
+      }\r
+\r
+      this.convertSimulationVth('json')\r
+\r
+      this.testInstanceService.create(this.testInstance)\r
+        .subscribe(\r
+          (result) => {\r
+            this.executionFailed = false;\r
+            this.createResult = result;\r
+            if (Object.keys(this.uploaders).length > 0)\r
+              this.uploadFiles(result);\r
+\r
+\r
+            this.execute.create({\r
+              _id: this.createResult._id,\r
+              async: true\r
+            })\r
+              .subscribe(\r
+                (response) => {\r
+\r
+                  this.childEvent.emit();\r
+                  this.snack.openFromComponent(AlertSnackbarComponent, {\r
+                    duration: 1500,\r
+                    data: {\r
+                      message: 'Test Instance Saved and Executed'\r
+                    }\r
+                  });\r
+                  this.router.navigateByUrl('/dashboard');\r
+                },\r
+                (error) => {\r
+                  this.executionFailed = true;\r
+                  this.dialog.open(AlertModalComponent, {\r
+                    width: '450px',\r
+                    data: {\r
+                      type: 'Alert',\r
+                      message: "Execution error: " + error\r
+                    }\r
+                  });\r
+                });\r
+          },\r
+          (error) => {\r
+            this.dialog.open(AlertModalComponent, {\r
+              width: '450px',\r
+              data: {\r
+                type: 'Alert',\r
+                message: "Save Error: " + error\r
+              }\r
+            });\r
+          });\r
+    }\r
+  }\r
+\r
+  createNewInstance() {\r
+    this.testInstance = {\r
+      'testInstanceName': '',\r
+      'testInstanceDescription': '',\r
+      'testDefinitionId': this.selectedDefinition._id,\r
+      'testData': '',\r
+      'simulationVthInput': {}\r
+\r
+    }\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.spec.ts b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.spec.ts
new file mode 100644 (file)
index 0000000..875e328
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { CreateTestInstanceFormModule } from './create-test-instance-form.module';\r
+\r
+describe('CreateTestInstanceFormModule', () => {\r
+  let createTestInstanceFormModule: CreateTestInstanceFormModule;\r
+\r
+  beforeEach(() => {\r
+    createTestInstanceFormModule = new CreateTestInstanceFormModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(createTestInstanceFormModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.ts b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.ts
new file mode 100644 (file)
index 0000000..3daf9ad
--- /dev/null
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { CreateTestInstanceFormComponent } from './create-test-instance-form.component';\r
+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { FormsModule } from '@angular/forms';\r
+import { MatButtonModule, MatDialogModule, MatCheckboxModule, MatRadioModule, MatInputModule, MatIconModule, MatExpansionModule, MatCardModule, MatOptionModule, MatSnackBarModule, MatProgressBar, MatSlideToggleModule, MatSelectModule } from '@angular/material';\r
+import { CodemirrorModule } from 'ng2-codemirror';\r
+import { SelectStrategyModalModule } from '../select-strategy-modal/select-strategy-modal.module';\r
+import { AlertModalModule } from '../alert-modal/alert-modal.module';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+import { FormGeneratorModule } from '../form-generator/form-generator.module';\r
+import { FileUploadModule } from 'ng2-file-upload';\r
+import { FilterNonDeployedPipe } from './filterNonDeployed.pipe';\r
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';\r
+import { WorkflowRequestModule } from '../workflow-request/workflow-request.module';\r
+\r
+\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    AlertModalModule,\r
+    SelectStrategyModalModule,\r
+    PerfectScrollbarModule,\r
+    FilterPipeModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatDialogModule,\r
+    MatCheckboxModule,\r
+    MatRadioModule,\r
+    MatInputModule,\r
+    MatSlideToggleModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatIconModule,\r
+    MatExpansionModule,\r
+    MatCardModule,\r
+    MatSnackBarModule,\r
+    AlertSnackbarModule,\r
+    CodemirrorModule,\r
+    FormGeneratorModule,\r
+    FileUploadModule,\r
+    NgbModule,\r
+    WorkflowRequestModule\r
+\r
+\r
+  ],\r
+  declarations: [CreateTestInstanceFormComponent, FilterNonDeployedPipe],\r
+  exports: [CreateTestInstanceFormComponent]\r
+})\r
+export class CreateTestInstanceFormModule {\r
+\r
+ }\r
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/filterNonDeployed.pipe.ts b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/filterNonDeployed.pipe.ts
new file mode 100644 (file)
index 0000000..87a33c3
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Pipe, PipeTransform } from '@angular/core';\r
+\r
+@Pipe({\r
+    name: 'filterNonDeployed',\r
+    pure: false\r
+})\r
+export class FilterNonDeployedPipe implements PipeTransform {\r
+    transform(items: any[], callback: (item: any) => boolean): any {\r
+        if (!items || !callback) {\r
+            return items;\r
+        }\r
+        // filter items array, items which match and return true will be\r
+        // kept, false will be filtered out\r
+        return items.filter(item => callback(item) );\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/create-test-instance-form/instance.class.ts b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/instance.class.ts
new file mode 100644 (file)
index 0000000..13af570
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export class TestInstance {\r
+\r
+}\r
+\r
+export class WorkflowRequest {\r
+    public async = false;\r
+    public asyncTopic = "";\r
+    public executorId = "";\r
+    public testInstanceId = "";\r
+    public pfloInput : null;\r
+    public testData : null;\r
+    public vthInput : null;\r
+    public maxExecutionTimeInMillis = 0;\r
+\r
+    constructor(instanceId){\r
+        this.testInstanceId = instanceId;\r
+    }\r
+}\r
+\r
+export class PfloInputClass {\r
+    public args = [];\r
+    public interruptOnFailure = false;\r
+    public maxFailures = 0;\r
+    public threadPoolSize = 2;\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.html b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.html
new file mode 100644 (file)
index 0000000..c8a4884
--- /dev/null
@@ -0,0 +1,23 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<form [formGroup]="form" style="text-align:center" #test >\r
+\r
+\r
+</form>\r
+<div class="col-md-12" >\r
+    <h5 [hidden]="!noData()">No Input Template provided.</h5>\r
+</div>\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.scss b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.spec.ts b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.spec.ts
new file mode 100644 (file)
index 0000000..0ba7d15
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { FormGeneratorComponent } from './form-generator.component';\r
+\r
+describe('FormGeneratorComponent', () => {\r
+  let component: FormGeneratorComponent;\r
+  let fixture: ComponentFixture<FormGeneratorComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ FormGeneratorComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(FormGeneratorComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.ts b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.ts
new file mode 100644 (file)
index 0000000..6f205db
--- /dev/null
@@ -0,0 +1,381 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, Output, EventEmitter, ViewChild, NgModuleRef, Injector, Compiler, NgModule, ViewContainerRef } from '@angular/core';\r
+import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';\r
+import { MatSnackBar } from '@angular/material';\r
+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';\r
+\r
+\r
+@Component({\r
+  selector: 'app-form-generator',\r
+  templateUrl: './form-generator.component.html',\r
+  styleUrls: ['./form-generator.component.scss']\r
+})\r
+export class FormGeneratorComponent implements OnInit {\r
+\r
+  public test = {\r
+    text1: "Hello please enter text1",\r
+    text2: "Hello please enter text2",\r
+    one: {\r
+      text1: "lol"\r
+    },\r
+    siteSpecific: {\r
+      port: "1234",\r
+      host: "google.com"\r
+    },\r
+    list: ["Enter", "some", "values"],\r
+    list2: [{"test": "hello"}, {"test2": "hello2"}]\r
+  }\r
+  public isSaved = false;\r
+  public arrayCheck;\r
+  @Input() public JSONData : any;\r
+  @Input() public taskId: any;\r
+  @ViewChild('test', { read: ViewContainerRef }) public containerDiv;\r
+  @Output() public childEvent = new EventEmitter();\r
+\r
+  form = new FormGroup({\r
+  });\r
+\r
+  //public textAreaTemplate = "<textarea [(value)]='";\r
+  /*\r
+  constructor(private _compiler: Compiler,\r
+            private _injector: Injector,\r
+            private _m: NgModuleRef<any>) {\r
+}\r
+\r
+ngAfterViewInit() {\r
+  const template = '<span>generated on the fly: {{name}}</span>';\r
+\r
+  const tmpCmp = Component({template: template})(class {\r
+  });\r
+  const tmpModule = NgModule({declarations: [tmpCmp]})(class {\r
+  });\r
+\r
+  this._compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+    .then((factories) => {\r
+      const f = factories.componentFactories[0];\r
+      const cmpRef = this.vc.createComponent(f);\r
+      cmpRef.instance.name = 'dynamic';\r
+    })\r
+}\r
+  */\r
\r
+  //public containerDiv;// = document.getElementById(tas);\r
+  constructor(private compiler: Compiler,\r
+    private injector: Injector,\r
+    private snack: MatSnackBar,\r
+    private m: NgModuleRef<any>) { }\r
+    public testing = "hello";\r
+    //public textAreaTemplate = '';\r
+  ngOnInit() {\r
+   \r
+   //this.JSONData = this.test;\r
+    if(this.JSONData){\r
+      this.arrayCheck = JSON.parse(JSON.stringify(this.JSONData));\r
+      let arr = [];\r
+      this.populatePage(arr, 1);\r
+      this.onFormChange();\r
+    }\r
+  }\r
+\r
+  onFormChange(){\r
+    \r
+    this.form.valueChanges.subscribe(val => {\r
+      this.copyValues([]);\r
+      \r
+      let event = {\r
+        object: this.JSONData,\r
+        taskId: this.taskId\r
+      };\r
+      this.childEvent.emit(event);\r
+    });\r
+  }\r
+  //checks if data was supplied to form\r
+  noData(){\r
+   \r
+    if(Object.keys(this.form.controls).length == 0){\r
+      return true;\r
+    }else{\r
+      return false;\r
+    }\r
+  }\r
+\r
+  copyValues(keyArr){\r
+    // console.log("Fixed");\r
+    let data = this.JSONData;\r
+    let tempArrCheck = this.arrayCheck;\r
+    let keyPath = "";\r
+    for(let k in keyArr){\r
+      tempArrCheck = tempArrCheck[keyArr[k]];\r
+      data = data[keyArr[k]];\r
+      keyPath += keyArr[k];\r
+    }\r
+    \r
+    for(let key in data){\r
+      if(this.form.get(keyPath + key)){\r
+        if(tempArrCheck[key] === "_ConvertThisArray_"){\r
+          let temp = this.form.get(keyPath + key).value;\r
+          data[key] = temp.split(',');\r
+        }else{\r
+          data[key] = this.form.get(keyPath + key).value;\r
+        }\r
+      }else{\r
+        keyArr.push(key);\r
+        this.copyValues(keyArr);\r
+        keyArr.splice(keyArr.length - 1);\r
+      }\r
+    }\r
+    // Object.keys(this.form.controls).forEach(key => {\r
+    //   data[key] = this.form.get(key).value;\r
+    // });\r
+    \r
+  }\r
+\r
+  populatePage(keyArr, level){//vthinput and testInput\r
+    let data = this.JSONData;\r
+    //used to detect and convert arrays after input is entered\r
+    let tempArrCheck = this.arrayCheck;\r
+    let keyPath = "";\r
+    for(let k in keyArr){\r
+      tempArrCheck = tempArrCheck[keyArr[k]];\r
+      data = data[keyArr[k]];\r
+      keyPath += keyArr[k];\r
+    }\r
+    //console.log(data);\r
+    \r
+    for( let key in data){\r
+      let indent = 'ml-' + level;\r
+      \r
+      if((typeof data[key] === "object" && !data[key].length) || (typeof data[key] === "object" && data[key].length && \r
+        typeof data[key][0] === "object")){\r
+        \r
+        let str = '';\r
+        if(level >= 4){\r
+          str = 'h5';\r
+          //indent = 'ml-5';\r
+        }else if(level === 3){\r
+          str = 'h4';\r
+          //indent = 'ml-4';\r
+        }else if (level === 2){\r
+          str = 'h3';\r
+          //indent = 'ml-3';\r
+        }else{\r
+          str = 'h2'\r
+          //indent = 'ml-2';\r
+        }\r
+        if(data.constructor === Array){\r
+          \r
+          keyArr.push(key);\r
+          this.populatePage(keyArr, level);\r
+          keyArr.splice(keyArr.length - 1);\r
+          continue;\r
+        }\r
+        const textHeaderTemplate = '<' + str + ' class="'+ indent +'" style="font-weight:bold">' + key.trim() + '</'+ str + '>';\r
+        const tmpCmp = Component({template: textHeaderTemplate})(class {\r
+        });\r
+        const tmpModule = NgModule({imports:[ReactiveFormsModule] ,declarations: [tmpCmp]})(class {\r
+        });\r
+             \r
+        this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+          .then((factories) => {\r
+            const f = factories.componentFactories[0];\r
+            const cmpRef = this.containerDiv.createComponent(f);\r
+          })\r
+        \r
+        keyArr.push(key);\r
+        level ++;\r
+        this.populatePage(keyArr, level);\r
+        level = level -1;\r
+        keyArr.splice(keyArr.length - 1);\r
+      }\r
+      else if(typeof data[key] === "string"){\r
+       // this.containerDiv.\r
+     \r
+       this.form.addControl(keyPath + key.trim(), new FormControl(data[key]));\r
+      \r
+        if(level > 1){\r
+          \r
+          const textInputTemplate = '<div class="  mb-1 '+ indent + '" [formGroup]="form"> <label class="mr-2">' + key.trim() + '</label><input formControlName="' + keyPath + key.trim() + '"> </div>';\r
+          const tmpCmp = Component({template: textInputTemplate})(class {\r
+          });\r
+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+          });\r
+          \r
+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+          \r
+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+            .then((factories) => {\r
+              const f = factories.componentFactories[0];\r
+              const cmpRef = this.containerDiv.createComponent(f);\r
+              cmpRef.instance.form = this.form;\r
+            })\r
+        }\r
+        else{\r
+          const textInputTemplate = '<div class="  mb-1 '+ indent + '" [formGroup]="form"> <h5 style="font-weight:bold" class="mr-2">' + key.trim() + '</h5><input formControlName="' + keyPath + key.trim() + '"> </div>';\r
+          const tmpCmp = Component({template: textInputTemplate})(class {\r
+          });\r
+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+          });\r
+          \r
+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+          \r
+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+            .then((factories) => {\r
+              const f = factories.componentFactories[0];\r
+              const cmpRef = this.containerDiv.createComponent(f);\r
+              cmpRef.instance.form = this.form;\r
+            })\r
+        }\r
+       \r
+        \r
+      }\r
+      else if(typeof data[key] === "object" && data[key].length){\r
+        \r
+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+        let temp = "";\r
+        for(let i = 0; i < data[key].length; i++){\r
+          if(i != data[key].length - 1)\r
+            temp += data[key][i] + ",";\r
+          else\r
+            temp += data[key][i] + "";\r
+        }\r
+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("textarea")).innerHTML = temp.trim();\r
+        this.form.addControl(keyPath + key.trim(), new FormControl(temp));\r
+        \r
+        tempArrCheck[key] = "_ConvertThisArray_";\r
+\r
+        if(level > 1){\r
+         \r
+          const textAreaTemplate = '<div class= "  mb-1 '+ indent + '" [formGroup]="form"> <label class="mr-2">' + key.trim() + '</label><textarea rows="' + data[key].length + '" formControlName="' + keyPath + key.trim() + '">  </textarea></div>';// + path + "'> "+ data[key] + "</textarea>"\r
+          const tmpCmp = Component({template: textAreaTemplate})(class {\r
+          });\r
+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+          });\r
+          \r
+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+          \r
+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+            .then((factories) => {\r
+              const f = factories.componentFactories[0];\r
+              const cmpRef = this.containerDiv.createComponent(f);\r
+              cmpRef.instance.form = this.form;\r
+            })\r
+        }\r
+        else{\r
+          const textAreaTemplate = '<div class= "  mb-1 '+ indent + '" [formGroup]="form"> <h5 style="font-weight:bold" class="mr-2">' + key.trim() + '</h5><textarea rows="' + data[key].length + '" formControlName="' + keyPath + key.trim() + '">  </textarea></div>';// + path + "'> "+ data[key] + "</textarea>"\r
+          const tmpCmp = Component({template: textAreaTemplate})(class {\r
+          });\r
+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+          });\r
+          \r
+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+          \r
+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+            .then((factories) => {\r
+              const f = factories.componentFactories[0];\r
+              const cmpRef = this.containerDiv.createComponent(f);\r
+              cmpRef.instance.form = this.form;\r
+            })\r
+        }\r
+        // const textAreaTemplate = '<div class= "mb-2" [formGroup]="form"> <label class="mr-2">' + key.trim() + '</label><textarea rows="' + data[key].length + '" formControlName="' + keyPath + key.trim() + '">  </textarea></div>';// + path + "'> "+ data[key] + "</textarea>"\r
+        // const tmpCmp = Component({template: textAreaTemplate})(class {\r
+        // });\r
+        // const tmpModule = NgModule({imports:[ReactiveFormsModule] ,declarations: [tmpCmp]})(class {\r
+        // });\r
+        \r
+        // //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+        \r
+        // this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+        //   .then((factories) => {\r
+        //     const f = factories.componentFactories[0];\r
+        //     const cmpRef = this.containerDiv.createComponent(f);\r
+        //     cmpRef.instance.form = this.form;\r
+        //   })\r
+        \r
+      }\r
+      else if(typeof data[key] === 'boolean'){\r
+        let str = '';\r
+        let str2 = 'h5';\r
+        let bold = ' style="font-weight:bold"'\r
+        if(level > 1){\r
+          str2 = 'label';\r
+          bold = '';\r
+        }\r
+        if(data[key]){\r
+          str = '<option [ngValue]="true">true</option><option [ngValue]="false">false</option>';\r
+        }else{\r
+          str = '<option [ngValue]="false">false</option><option [ngValue]="true">true</option>';\r
+        }\r
+        this.form.addControl(keyPath + key.trim(), new FormControl(data[key]));\r
+        const textAreaTemplate = '<div class= "  mb-1 '+ indent + '" [formGroup]="form"> <' + str2 + bold + ' class="mr-2">' + key.trim() + '</' + str2 + '><select formControlName="' + keyPath + key.trim() +  '">' + str + ' </select></div>';// + path + "'> "+ data[key] + "</textarea>"\r
+          const tmpCmp = Component({template: textAreaTemplate})(class {\r
+        });\r
+        const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+        });\r
+        \r
+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+        \r
+        this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+          .then((factories) => {\r
+            const f = factories.componentFactories[0];\r
+            const cmpRef = this.containerDiv.createComponent(f);\r
+            cmpRef.instance.form = this.form;\r
+          })\r
+      }\r
+      else if(typeof data[key] === typeof 23){\r
+        let str = 'h5';\r
+        let bold = ' style="font-weight:bold"';\r
+        if(level > 1){\r
+          str = 'label';\r
+          bold = '';\r
+        }\r
+        this.form.addControl(keyPath + key.trim(), new FormControl(data[key]));\r
+        const textInputTemplate = '<div class="  mb-1 '+ indent + '" [formGroup]="form"> <' + str + bold + ' class="mr-2">' + key.trim() + '</' + str + '><input type="number" formControlName="' + keyPath + key.trim() + '"> </div>';\r
+          const tmpCmp = Component({template: textInputTemplate})(class {\r
+        });\r
+        const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+        });\r
+        \r
+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+        \r
+        this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+          .then((factories) => {\r
+            const f = factories.componentFactories[0];\r
+            const cmpRef = this.containerDiv.createComponent(f);\r
+            cmpRef.instance.form = this.form;\r
+          })\r
+      }\r
+      else{\r
+        const textAreaTemplate = ' <h5 style="font-weight:bold" class="mr-2 '+ indent + '">' + key.trim() + ': Type Not Supported</h5>';// + path + "'> "+ data[key] + "</textarea>"\r
+          const tmpCmp = Component({template: textAreaTemplate})(class {\r
+          });\r
+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {\r
+          });\r
+          \r
+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();\r
+          \r
+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)\r
+            .then((factories) => {\r
+              const f = factories.componentFactories[0];\r
+              const cmpRef = this.containerDiv.createComponent(f);\r
+              cmpRef.instance.form = this.form;\r
+            })\r
+      }\r
+\r
+    }\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.spec.ts b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.spec.ts
new file mode 100644 (file)
index 0000000..4fe9215
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { FormGeneratorModule } from './form-generator.module';\r
+\r
+describe('FormGeneratorModule', () => {\r
+  let formGeneratorModule: FormGeneratorModule;\r
+\r
+  beforeEach(() => {\r
+    formGeneratorModule = new FormGeneratorModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(formGeneratorModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.ts b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.ts
new file mode 100644 (file)
index 0000000..6d418cd
--- /dev/null
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { FormGeneratorComponent } from './form-generator.component';\r
+import { MatButtonModule, MatSnackBarModule } from '@angular/material';\r
+import { TextAreaComponent } from './text-area/text-area.component';\r
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    MatButtonModule,\r
+    FormsModule,\r
+    ReactiveFormsModule,\r
+    MatSnackBarModule,\r
+    AlertSnackbarModule\r
+  ],\r
+  declarations: [ FormGeneratorComponent, TextAreaComponent ],\r
+  exports: [ FormGeneratorComponent ]\r
+})\r
+export class FormGeneratorModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.html b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.html
new file mode 100644 (file)
index 0000000..5c3c3aa
--- /dev/null
@@ -0,0 +1,17 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<textarea [(value)]="textValue"></textarea>\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.scss b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.spec.ts b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.spec.ts
new file mode 100644 (file)
index 0000000..3684549
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TextAreaComponent } from './text-area.component';\r
+\r
+describe('TextAreaComponent', () => {\r
+  let component: TextAreaComponent;\r
+  let fixture: ComponentFixture<TextAreaComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TextAreaComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TextAreaComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.ts b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.ts
new file mode 100644 (file)
index 0000000..d144d17
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+\r
+@Component({\r
+  selector: 'app-text-area',\r
+  templateUrl: './text-area.component.html',\r
+  styleUrls: ['./text-area.component.scss']\r
+})\r
+export class TextAreaComponent implements OnInit {\r
+  public textValue = "hello";\r
+  constructor() { }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/index.ts b/otf-frontend/client/src/app/shared/modules/index.ts
new file mode 100644 (file)
index 0000000..97d2c90
--- /dev/null
@@ -0,0 +1,18 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export * from './page-header/page-header.module';\r
+// export * from './stat/stat.module';\r
diff --git a/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.pug b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.pug
new file mode 100644 (file)
index 0000000..9fb2178
--- /dev/null
@@ -0,0 +1,18 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+p onboard-mechid works!\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.scss b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.spec.ts b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.spec.ts
new file mode 100644 (file)
index 0000000..5c404d8
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { OnboardMechidComponent } from './onboard-mechid.component';\r
+\r
+describe('OnboardMechidComponent', () => {\r
+  let component: OnboardMechidComponent;\r
+  let fixture: ComponentFixture<OnboardMechidComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ OnboardMechidComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(OnboardMechidComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.ts b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.ts
new file mode 100644 (file)
index 0000000..1f61f0a
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';\r
+\r
+@Component({\r
+  selector: 'app-onboard-mechid',\r
+  templateUrl: './onboard-mechid.component.pug',\r
+  styleUrls: ['./onboard-mechid.component.scss']\r
+})\r
+export class OnboardMechidComponent implements OnInit {\r
+\r
+  constructor(\r
+    public dialogRef: MatDialogRef<OnboardMechidComponent>,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+    ) { \r
+  \r
+  }\r
+\r
+  ngOnInit() {\r
+  }\r
+\r
+  onboardMechid(){\r
+    //call rohans api endpoint\r
+    //save mechId as a user\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.spec.ts b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.spec.ts
new file mode 100644 (file)
index 0000000..32d6392
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { OnboardMechidModule } from './onboard-mechid.module';\r
+\r
+describe('OnboardMechidModule', () => {\r
+  let onboardMechidModule: OnboardMechidModule;\r
+\r
+  beforeEach(() => {\r
+    onboardMechidModule = new OnboardMechidModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(onboardMechidModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.ts b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.ts
new file mode 100644 (file)
index 0000000..1a44865
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { OnboardMechidComponent } from './onboard-mechid.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule\r
+  ],\r
+  declarations: [OnboardMechidComponent],\r
+  exports: [OnboardMechidComponent],\r
+  entryComponents: [OnboardMechidComponent]\r
+})\r
+export class OnboardMechidModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.html b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.html
new file mode 100644 (file)
index 0000000..baa008a
--- /dev/null
@@ -0,0 +1,31 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<div class="row">\r
+    <div class="col-xl-12">\r
+        <h2 class="page-header">\r
+            {{heading}}\r
+        </h2>\r
+        <!-- Breadcumbs\r
+        <ol class="breadcrumb">\r
+            <li class="breadcrumb-item">\r
+                <i class="fa fa-dashboard"></i> <a href="Javascript:void(0)" [routerLink]="['/dashboard']">Dashboard</a>\r
+            </li>\r
+            <li class="breadcrumb-item active"><i class="fa {{icon}}"></i> {{heading}}</li>\r
+        </ol>\r
+        -->\r
+    </div>\r
+</div>\r
diff --git a/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.scss b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.spec.ts b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.spec.ts
new file mode 100644 (file)
index 0000000..d713226
--- /dev/null
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'\r
+import { RouterTestingModule } from '@angular/router/testing'\r
+\r
+import { PageHeaderComponent } from './page-header.component'\r
+import { PageHeaderModule } from './page-header.module'\r
+\r
+describe('PageHeaderComponent', () => {\r
+  let component: PageHeaderComponent\r
+  let fixture: ComponentFixture<PageHeaderComponent>\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      imports: [PageHeaderModule, RouterTestingModule],\r
+    })\r
+    .compileComponents()\r
+  }))\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(PageHeaderComponent)\r
+    component = fixture.componentInstance\r
+    fixture.detectChanges()\r
+  })\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy()\r
+  })\r
+})\r
diff --git a/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.ts b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.ts
new file mode 100644 (file)
index 0000000..5c4b5bf
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input } from '@angular/core';\r
+import { RouterModule } from '@angular/router';\r
+\r
+@Component({\r
+    selector: 'app-page-header',\r
+    templateUrl: './page-header.component.html',\r
+    styleUrls: ['./page-header.component.scss']\r
+})\r
+export class PageHeaderComponent implements OnInit {\r
+    @Input() heading: string;\r
+    @Input() icon: string;\r
+    constructor() {}\r
+\r
+    ngOnInit() {}\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/page-header/page-header.module.spec.ts b/otf-frontend/client/src/app/shared/modules/page-header/page-header.module.spec.ts
new file mode 100644 (file)
index 0000000..c76516b
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { PageHeaderModule } from './page-header.module';\r
+\r
+describe('PageHeaderModule', () => {\r
+  let pageHeaderModule: PageHeaderModule;\r
+\r
+  beforeEach(() => {\r
+    pageHeaderModule = new PageHeaderModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(pageHeaderModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/page-header/page-header.module.ts b/otf-frontend/client/src/app/shared/modules/page-header/page-header.module.ts
new file mode 100644 (file)
index 0000000..e0d2d7b
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { RouterModule } from '@angular/router';\r
+\r
+import { PageHeaderComponent } from './page-header.component';\r
+\r
+@NgModule({\r
+    imports: [CommonModule, RouterModule],\r
+    declarations: [PageHeaderComponent],\r
+    exports: [PageHeaderComponent]\r
+})\r
+export class PageHeaderModule {}\r
diff --git a/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.pug b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.pug
new file mode 100644 (file)
index 0000000..72b7884
--- /dev/null
@@ -0,0 +1,72 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+h2.mb-1(mat-dialog-title) Schedule {{selectedTestInstance ? selectedTestInstance.testInstanceName : ''}}\r
+\r
+mat-dialog-content(*ngIf="selectedTestInstance")\r
+  .row\r
+    .col-sm-6\r
+      h5 Create Schedule\r
+      .row\r
+        .col-sm-6\r
+          mat-form-field\r
+            input(matInput, [matDatepicker]="schedulePicker", [(ngModel)]='startDate', placeholder="Select Start Date", required)\r
+            mat-datepicker-toggle(matSuffix, [for]="schedulePicker")\r
+            mat-datepicker(#schedulePicker)\r
+        .col-sm-6\r
+          mat-form-field\r
+            input(matInput, [(ngModel)]="timeToRun", [ngxTimepicker]="picker", placeholder="Select Time", required)\r
+            ngx-material-timepicker(#picker)\r
+\r
+      .row.mb-2\r
+        .col-12\r
+          mat-slide-toggle(color="primary", [(ngModel)]="frequency") Add Frequency\r
+\r
+      .row(*ngIf="frequency").mb-2\r
+        .col-sm-12\r
+          mat-form-field.mr-2\r
+            input(matInput, type="number",  [(ngModel)]='numUnit', placeholder='Execution Interval', required)\r
+          mat-form-field\r
+            mat-select(placeholder='Time Unit', [(ngModel)]='timeUnit', required)\r
+              mat-option([value]=60) min(s)\r
+              mat-option([value]=3600) hour(s)\r
+              mat-option([value]=86400) day(s)\r
+        .col-sm-6\r
+          mat-form-field\r
+            input(matInput, [matDatepicker]="schedulePicker2",  [(ngModel)]='endDate', placeholder="Select a End Date (Optional)")\r
+            mat-datepicker-toggle(matSuffix, [for]="schedulePicker2")\r
+            mat-datepicker(#schedulePicker2)\r
+      \r
+      .row\r
+        .col-12\r
+          button(mat-raised-button, color="primary", (click)='createSchedule()') Create Schedule\r
+\r
+    .col-sm-6\r
+      h5 Scheduled Runs\r
+      .row(*ngIf="scheduledJobs")\r
+        .col-12\r
+          .group-list\r
+            .group-list-item(*ngFor="let job of scheduledJobs") \r
+              a((click)="deleteJob(job)") \r
+                i.fa.fa-times \r
+              |  {{ job.data.testSchedule._testInstanceStartDate }} {{job.data.testSchedule._testInstanceEndDate ? 'to ' + job.data.testSchedule._testInstanceEndDate : '' }} {{ job.data.testSchedule._testInstanceExecFreqInSeconds ? 'every ' + job.data.testSchedule._testInstanceExecFreqInSeconds + ' sec' : '' }}\r
+            .group-list-item(*ngIf="!loadingJobs && scheduledJobs.length == 0", style="text-align:center") Nothing is scheduled\r
+            .group-list-item(*ngIf="loadingJobs")\r
+              mat-spinner(style="margin:auto")\r
+mat-dialog-actions.pull-right\r
+  button.pull-right(mat-button, mat-dialog-close) Close\r
+    // The mat-dialog-close directive optionally accepts a value as a result for the dialog.\r
+    \r
diff --git a/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.scss b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..68e0f26
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ScheduleTestModalComponent } from './schedule-test-modal.component';\r
+\r
+describe('ScheduleTestModalComponent', () => {\r
+  let component: ScheduleTestModalComponent;\r
+  let fixture: ComponentFixture<ScheduleTestModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ScheduleTestModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ScheduleTestModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.ts b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.ts
new file mode 100644 (file)
index 0000000..e87e6dd
--- /dev/null
@@ -0,0 +1,190 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, Inject, OnInit } from '@angular/core';\r
+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material';\r
+import { TestInstanceService } from '../../services/test-instance.service';\r
+import { SchedulingService } from '../../services/scheduling.service';\r
+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';\r
+import { AlertModalComponent } from '../alert-modal/alert-modal.component';\r
+\r
+\r
+@Component({\r
+  selector: 'app-schedule-test-modal',\r
+  templateUrl: './schedule-test-modal.component.pug',\r
+  styleUrls: ['./schedule-test-modal.component.scss']\r
+})\r
+export class ScheduleTestModalComponent implements OnInit {\r
+\r
+  public data;\r
+  public test_instances;\r
+  public selectedTestInstance;\r
+  public schedule;\r
+  public search;\r
+  public timeUnit;\r
+  public timeToRun;\r
+  public numUnit;\r
+  public startDate;\r
+  public endDate;\r
+  public frequency = false;\r
+  public isSelected = false;\r
+  public scheduledJobs;\r
+  public loadingJobs;\r
+\r
+  constructor(\r
+    private schedulingService: SchedulingService,\r
+    private testInstanceService: TestInstanceService,\r
+    public dialogRef: MatDialogRef<ScheduleTestModalComponent>,\r
+    private snack: MatSnackBar,\r
+    private dialog: MatDialog,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) {\r
+\r
+  }\r
+\r
+  onNoClick(): void {\r
+    this.dialogRef.close();\r
+  }\r
+\r
+  ngOnInit() {\r
+    this.timeUnit = 60;\r
+    this.numUnit = 0;\r
+    this.search = {};\r
+    this.selectedTestInstance = {};\r
+    this.startDate = null;\r
+    this.timeToRun = null;\r
+    this.endDate = null;\r
+    //this.search.testInstanceName = ""; \r
+    //this.test_instances = [];\r
+    this.schedule = {};\r
+    this.schedule.testInstanceExecFreqInSeconds = '';\r
+    this.scheduledJobs = [];\r
+    this.loadingJobs = true;\r
+\r
+    //console.log(this.test_instances);\r
+    this.testInstanceService.get(this.input_data.id).subscribe(\r
+      result => {\r
+        this.selectedTestInstance = result;\r
+      }\r
+    );\r
+\r
+    this.schedulingService.find({$limit: -1, testInstanceId: this.input_data.id}).subscribe(\r
+      result => {\r
+        for (let i = 0; i < result['length']; i++) {\r
+          result[i].data.testSchedule._testInstanceStartDate = new Date(result[i].data.testSchedule._testInstanceStartDate).toLocaleString();\r
+          if (result[i].data.testSchedule._testInstanceEndDate) {\r
+            result[i].data.testSchedule._testInstanceEndDate = new Date(result[i].data.testSchedule._testInstanceEndDate).toLocaleString();\r
+          }\r
+          this.scheduledJobs.push(result[i]);\r
+\r
+        }\r
+        this.loadingJobs = false;\r
+      }\r
+    );\r
+  }\r
+\r
+  convertDate(date, time = ''): Date {\r
+    let nDate = new Date(date + '');\r
+    return new Date(nDate.getMonth() + 1 + '/' + nDate.getDate() + '/' + nDate.getFullYear() + ' ' + time);\r
+  }\r
+\r
+  createSchedule() {\r
+    this.convertDate(this.startDate, this.timeToRun);\r
+\r
+    if (!this.selectedTestInstance || !this.startDate || !this.timeToRun) {\r
+      this.dialog.open(AlertModalComponent, {\r
+        width: '450px',\r
+        data: {\r
+          type: 'Alert',\r
+          message: 'Select start date/time before you create schedule!'\r
+        }\r
+      });\r
+      return;\r
+    }\r
+    if (this.frequency) {\r
+      this.schedule = {\r
+        testInstanceId: this.selectedTestInstance._id,\r
+        testInstanceStartDate: this.convertDate(this.startDate, this.timeToRun).toISOString(),\r
+        testInstanceExecFreqInSeconds: this.numUnit * this.timeUnit,\r
+        async: false,\r
+        asyncTopic: ''\r
+      };\r
+      \r
+\r
+      if(this.endDate){\r
+        this.schedule.testInstanceEndDate = this.convertDate(this.endDate).toISOString();\r
+      }\r
+    } else {\r
+      this.schedule = {\r
+        testInstanceId: this.selectedTestInstance._id,\r
+        testInstanceStartDate: this.convertDate(this.startDate, this.timeToRun).toISOString(),\r
+        async: false,\r
+        asyncTopic: ''\r
+      };\r
+      //console.log(this.schedule);\r
+      \r
+    }\r
+\r
+    this.schedulingService.create(this.schedule).subscribe((result) => {\r
+      this.snack.openFromComponent(AlertSnackbarComponent, {\r
+        duration: 1500,\r
+        data: {\r
+          message: 'Schedule Created!'\r
+        }\r
+      });\r
+      this.ngOnInit();\r
+    }, err => {\r
+      this.dialog.open(AlertModalComponent, {\r
+        data: {\r
+          type: "alert", \r
+          message: err.message\r
+        }\r
+      })\r
+    })\r
+    // console.log(this.schedule);\r
+  }\r
+\r
+  deleteJob(job) {\r
+    var deleteJob = this.dialog.open(AlertModalComponent, {\r
+      width: '250px',\r
+      data: {\r
+        type: 'confirmation',\r
+        message: 'Are you sure you want to delete this schedule?'\r
+      }\r
+    });\r
+\r
+    deleteJob.afterClosed().subscribe(\r
+      result => {\r
+        if (result) {\r
+          this.schedulingService.delete(job._id).subscribe(\r
+            result => {\r
+              this.ngOnInit();\r
+            }\r
+          );\r
+        }\r
+      }\r
+    );\r
+  }\r
+  // this.testInstanceId = testInstanceId;\r
+  // this.testInstanceStartDate = testInstanceStartDate;\r
+  // this.testInstanceExecFreqInSeconds = testInstanceExecFreqInSeconds;\r
+  // this.testInstanceEndDate = testInstanceEndDate;\r
+  // this.async = async;\r
+  // this.asyncTopic = asyncTopic;\r
+  // this.executorId = executorId;\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..7ce918e
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ScheduleTestModalModule } from './schedule-test-modal.module';\r
+\r
+describe('ScheduleTestModalModule', () => {\r
+  let scheduleTestModalModule: ScheduleTestModalModule;\r
+\r
+  beforeEach(() => {\r
+    scheduleTestModalModule = new ScheduleTestModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(scheduleTestModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.ts b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.ts
new file mode 100644 (file)
index 0000000..ed9a26d
--- /dev/null
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+//needed imports for Material Dialogue\r
+import { MatDialogModule, MatRadioModule, MatIconModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatDatepickerModule, MatNativeDateModule, MatCheckboxModule, MatSelectModule, MatSnackBarModule, MatSlideToggleModule, MatProgressSpinnerModule} from '@angular/material';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { FormsModule } from '@angular/forms';\r
+import { ScheduleTestModalComponent } from './schedule-test-modal.component';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+import { AlertModalModule } from '../alert-modal/alert-modal.module';\r
+import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatRadioModule,\r
+    MatDialogModule,\r
+    MatDatepickerModule,\r
+    MatSnackBarModule,\r
+    AlertSnackbarModule,\r
+    AlertModalModule,\r
+    MatSnackBarModule,\r
+    MatSelectModule,\r
+    MatNativeDateModule,\r
+    MatCheckboxModule,\r
+    MatIconModule,\r
+    NgxMaterialTimepickerModule.forRoot(),\r
+    MatFormFieldModule,\r
+    MatSlideToggleModule,\r
+    MatProgressSpinnerModule\r
+  ],\r
+  declarations: [ScheduleTestModalComponent ],\r
+  exports: [ScheduleTestModalComponent],\r
+  entryComponents: [ScheduleTestModalComponent]\r
+})\r
+export class ScheduleTestModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.pug b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.pug
new file mode 100644 (file)
index 0000000..9785f93
--- /dev/null
@@ -0,0 +1,35 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+h2.mb-1(mat-dialog-title) Select Test Definition\r
+//input.bg-light.form-control(mat-dialog-title, type="text", placeholder="Search...", [(ngModel)]="search.testName")\r
+mat-form-field(style="width:100%")\r
+  input(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.testName')\r
+  button(mat-button, *ngIf='search.testName', matSuffix, mat-icon-button, aria-label='Clear', (click)="search.testName=''")\r
+    mat-icon close\r
+h5([hidden]='test_definitions.length != 0') No Test Definitions found.\r
+mat-dialog-content\r
+  .list-group\r
+    mat-radio-group([(ngModel)]="input_data.testDefinition")\r
+      .list-group-item(*ngFor="let testDefinition of test_definitions | filterBy:search")\r
+        mat-radio-button([value]="testDefinition")\r
+          .ml-2\r
+            h5 {{ testDefinition.testName }} \r
+            p.mb-0 {{ testDefinition.testDescription}}\r
+mat-dialog-actions\r
+    button(mat-button, mat-dialog-close) Cancel \r
+    // The mat-dialog-close directive optionally accepts a value as a result for the dialog.\r
+    button.bg-primary.text-white(mat-button, [mat-dialog-close]='input_data.testDefinition') Select\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.scss b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..b318dad
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { SelectStrategyModalComponent } from './select-strategy-modal.component';\r
+\r
+describe('SelectStrategyModalComponent', () => {\r
+  let component: SelectStrategyModalComponent;\r
+  let fixture: ComponentFixture<SelectStrategyModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ SelectStrategyModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(SelectStrategyModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.ts b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.ts
new file mode 100644 (file)
index 0000000..371c8ae
--- /dev/null
@@ -0,0 +1,65 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, Inject, OnInit } from '@angular/core';\r
+import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';\r
+import { TestDefinitionService } from '../../services/test-definition.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+\r
+@Component({\r
+  selector: 'app-select-strategy-modal',\r
+  templateUrl: './select-strategy-modal.component.pug',\r
+  styleUrls: ['./select-strategy-modal.component.scss']\r
+})\r
+export class SelectStrategyModalComponent implements OnInit  {\r
+\r
+  public data; \r
+  public test_definitions; \r
+  public search;\r
+\r
+  constructor(\r
+    public dialogRef: MatDialogRef<SelectStrategyModalComponent>,\r
+    private testDefinitionService: TestDefinitionService, \r
+    private _groups: GroupService,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) {\r
+    this.data = {};\r
+   }\r
+  \r
+  onNoClick(): void {\r
+    this.dialogRef.close();\r
+  }\r
+\r
+  ngOnInit() {\r
+    this.test_definitions = [];\r
+    let groupId = this._groups.getGroup()['_id'];\r
+    \r
+    this.testDefinitionService.find({$limit: -1, groupId: groupId, disabled: { $ne: true }, 'bpmnInstances.isDeployed': true, $populate: ['bpmnInstances.testHeads.testHeadId'] })\r
+      .subscribe(\r
+        (result) => {\r
+            this.test_definitions = result;\r
+        },\r
+        (error) => {\r
+            console.log(error);\r
+      });\r
+\r
+    \r
+    this.search = {};\r
+    this.search.testName = ""; \r
+    this.input_data.testDefinition = {};\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..edebb83
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { SelectStrategyModalModule } from './select-strategy-modal.module';\r
+\r
+describe('SelectStrategyModalModule', () => {\r
+  let selectStrategyModalModule: SelectStrategyModalModule;\r
+\r
+  beforeEach(() => {\r
+    selectStrategyModalModule = new SelectStrategyModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(selectStrategyModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.ts b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.ts
new file mode 100644 (file)
index 0000000..fca9f67
--- /dev/null
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { SelectStrategyModalComponent } from './select-strategy-modal.component';\r
+import { MatDialogModule, MatRadioModule, MatIconModule, MatFormFieldModule, MatInputModule, MatButtonModule } from '@angular/material';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { FormsModule } from '@angular/forms';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatRadioModule,\r
+    MatDialogModule,\r
+    MatIconModule,\r
+    MatFormFieldModule\r
+  ],\r
+  declarations: [SelectStrategyModalComponent],\r
+  exports: [SelectStrategyModalComponent],\r
+  entryComponents: [SelectStrategyModalComponent]\r
+})\r
+export class SelectStrategyModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.pug b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.pug
new file mode 100644 (file)
index 0000000..da2d0c0
--- /dev/null
@@ -0,0 +1,30 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+h2(mat-dialog-title) Select Test Head\r
+input.bg-light.form-control(mat-dialog-title, type="text", placeholder="Search...", [(ngModel)]="search.testHeadName")\r
+mat-dialog-content\r
+  .list-group\r
+    mat-radio-group([(ngModel)]="input_data.testHead")\r
+      .list-group-item(*ngFor="let testHead of test_heads | filterBy:search")\r
+        mat-radio-button([value]="testHead")\r
+          .ml-2\r
+            h5 {{ testHead.testHeadName }}\r
+            p.mb-0 {{ testHead.testHeadDescription }}\r
+mat-dialog-actions\r
+    button(mat-button, mat-dialog-close) Cancel\r
+    // The mat-dialog-close directive optionally accepts a value as a result for the dialog.\r
+    button.bg-primary.text-white(mat-button, [mat-dialog-close]='input_data.testHead') Select
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.scss b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..b15d850
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { SelectTestHeadModalComponent } from './select-test-head-modal.component';\r
+\r
+describe('SelectTestHeadModalComponent', () => {\r
+  let component: SelectTestHeadModalComponent;\r
+  let fixture: ComponentFixture<SelectTestHeadModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ SelectTestHeadModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(SelectTestHeadModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.ts b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.ts
new file mode 100644 (file)
index 0000000..9c81b59
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';\r
+import { TestHeadService } from '../../services/test-head.service';\r
+\r
+@Component({\r
+  selector: 'app-select-test-head-modal',\r
+  templateUrl: './select-test-head-modal.component.pug',\r
+  styleUrls: ['./select-test-head-modal.component.scss']\r
+})\r
+export class SelectTestHeadModalComponent implements OnInit {\r
+\r
+  public data = {test_heads: []};\r
+  public test_heads;\r
+  public search;\r
+  public selected;\r
+\r
+  constructor(public dialogRef: MatDialogRef<SelectTestHeadModalComponent>,\r
+    private testHeadService: TestHeadService,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) { }\r
+\r
+  ngOnInit() {\r
+    this.search = {};\r
+    this.input_data.testHead = {};\r
+    this.test_heads = [{}];\r
+    this.testHeadService.find({$limit: -1})\r
+      .subscribe(\r
+        (result) => {\r
+            this.test_heads = result;\r
+        },\r
+        (error) => {\r
+            alert(error.error.message);\r
+      });\r
+    //console.log(this.test_heads)\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..ed2b5df
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { SelectTestHeadModalModule } from './select-test-head-modal.module';\r
+\r
+describe('SelectTestHeadModalModule', () => {\r
+  let selectTestHeadModalModule: SelectTestHeadModalModule;\r
+\r
+  beforeEach(() => {\r
+    selectTestHeadModalModule = new SelectTestHeadModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(selectTestHeadModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.ts b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.ts
new file mode 100644 (file)
index 0000000..7a11ef6
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { SelectTestHeadModalComponent } from './select-test-head-modal.component';\r
+import { MAT_DIALOG_DEFAULT_OPTIONS, MatRadioModule, MatDialogModule, MatInputModule, MatButtonModule, MatFormFieldModule, MatIconModule } from '@angular/material';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatRadioModule,\r
+    MatDialogModule,\r
+    MatIconModule,\r
+    MatFormFieldModule\r
+  ],\r
+  declarations: [SelectTestHeadModalComponent],\r
+  exports: [SelectTestHeadModalComponent],\r
+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}],\r
+  entryComponents: [SelectTestHeadModalComponent]\r
+})\r
+export class SelectTestHeadModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/stat/stat.component.html b/otf-frontend/client/src/app/shared/modules/stat/stat.component.html
new file mode 100644 (file)
index 0000000..02cd2ba
--- /dev/null
@@ -0,0 +1,35 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<div class="card text-white bg-{{bgClass}}">\r
+    <div class="card-header">\r
+        <div class="row">\r
+            <div class="col col-xs-3">\r
+                <i class="fa {{icon}} fa-5x"></i>\r
+            </div>\r
+            <div class="col col-xs-9 text-right">\r
+                <div class="d-block huge">{{count}}</div>\r
+                <div class="d-block">{{label}}</div>\r
+            </div>\r
+        </div>\r
+    </div>\r
+    <div class="card-footer">\r
+        <span class="float-left">View Details {{data}}</span>\r
+        <a href="javascript:void(0)" class="float-right card-inverse">\r
+            <span ><i class="fa fa-arrow-circle-right"></i></span>\r
+        </a>\r
+    </div>\r
+</div>\r
diff --git a/otf-frontend/client/src/app/shared/modules/stat/stat.component.scss b/otf-frontend/client/src/app/shared/modules/stat/stat.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/stat/stat.component.spec.ts b/otf-frontend/client/src/app/shared/modules/stat/stat.component.spec.ts
new file mode 100644 (file)
index 0000000..903c861
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { StatComponent } from './stat.component';\r
+\r
+describe('StatComponent', () => {\r
+    let component: StatComponent;\r
+    let fixture: ComponentFixture<StatComponent>;\r
+\r
+    beforeEach(\r
+        async(() => {\r
+            TestBed.configureTestingModule({\r
+                declarations: [StatComponent]\r
+            }).compileComponents();\r
+        })\r
+    );\r
+\r
+    beforeEach(() => {\r
+        fixture = TestBed.createComponent(StatComponent);\r
+        component = fixture.componentInstance;\r
+        fixture.detectChanges();\r
+    });\r
+\r
+    it('should create', () => {\r
+        expect(component).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/stat/stat.component.ts b/otf-frontend/client/src/app/shared/modules/stat/stat.component.ts
new file mode 100644 (file)
index 0000000..d5ff3e4
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';\r
+\r
+@Component({\r
+    selector: 'app-stat',\r
+    templateUrl: './stat.component.html',\r
+    styleUrls: ['./stat.component.scss']\r
+})\r
+export class StatComponent implements OnInit {\r
+    @Input() bgClass: string;\r
+    @Input() icon: string;\r
+    @Input() count: number;\r
+    @Input() label: string;\r
+    @Input() data: number;\r
+    @Output() event: EventEmitter<any> = new EventEmitter();\r
+\r
+    constructor() {}\r
+\r
+    ngOnInit() {}\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/stat/stat.module.spec.ts b/otf-frontend/client/src/app/shared/modules/stat/stat.module.spec.ts
new file mode 100644 (file)
index 0000000..b089e89
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { StatModule } from './stat.module';\r
+\r
+describe('StatModule', () => {\r
+    let statModule: StatModule;\r
+\r
+    beforeEach(() => {\r
+        statModule = new StatModule();\r
+    });\r
+\r
+    it('should create an instance', () => {\r
+        expect(statModule).toBeTruthy();\r
+    });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/stat/stat.module.ts b/otf-frontend/client/src/app/shared/modules/stat/stat.module.ts
new file mode 100644 (file)
index 0000000..211413a
--- /dev/null
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { StatComponent } from './stat.component';\r
+\r
+@NgModule({\r
+    imports: [CommonModule],\r
+    declarations: [StatComponent],\r
+    exports: [StatComponent]\r
+})\r
+export class StatModule {}\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.pug b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.pug
new file mode 100644 (file)
index 0000000..5edf087
--- /dev/null
@@ -0,0 +1,25 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(style="position:relative") \r
+  .row()\r
+    .col-12\r
+      h2.pull-left(mat-dialog-title) Test Definition\r
+      button.pull-right(mat-icon-button, (click)="close()")\r
+        mat-icon close\r
+  .row\r
+    mat-dialog-content\r
+      app-create-test-form(*ngIf="formData", [formData]="formData", (childEvent)="close($event)")\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.scss b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..d932d95
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestDefinitionModalComponent } from './test-definition-modal.component';\r
+\r
+describe('TestDefinitionModalComponent', () => {\r
+  let component: TestDefinitionModalComponent;\r
+  let fixture: ComponentFixture<TestDefinitionModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestDefinitionModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestDefinitionModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.ts b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.ts
new file mode 100644 (file)
index 0000000..3e681ed
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject, Output, Input } from '@angular/core';\r
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';\r
+import { AppGlobals } from 'app/app.global';\r
+import { HttpClient } from '@angular/common/http';\r
+import { TestDefinitionService } from 'app/shared/services/test-definition.service';\r
+\r
+@Component({\r
+  selector: 'app-test-definition-modal',\r
+  templateUrl: './test-definition-modal.component.pug',\r
+  styleUrls: ['./test-definition-modal.component.scss']\r
+})\r
+export class TestDefinitionModalComponent implements OnInit {\r
+\r
+  @Output() formData;\r
+\r
+  @Input() childEvent;\r
+\r
+  constructor(\r
+      public dialogRef: MatDialogRef<TestDefinitionModalComponent>,\r
+      private http: HttpClient,\r
+      private testDefinition: TestDefinitionService,\r
+    @Inject(MAT_DIALOG_DATA) public input_data) { }\r
+\r
+  ngOnInit() {\r
+    if (this.input_data.testDefinitionId) {\r
+      this.testDefinition.get(this.input_data.testDefinitionId).subscribe(result => {\r
+        this.formData = result;\r
+      });\r
+    } else {\r
+      this.formData = 'new';\r
+    }\r
+  }\r
+\r
+  close() {\r
+    this.dialogRef.close();\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..245e8dc
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestDefinitionModalModule } from './test-definition-modal.module';\r
+\r
+describe('TestDefinitionModalModule', () => {\r
+  let testDefinitionModalModule: TestDefinitionModalModule;\r
+\r
+  beforeEach(() => {\r
+    testDefinitionModalModule = new TestDefinitionModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(testDefinitionModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.ts b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.ts
new file mode 100644 (file)
index 0000000..d136b4b
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { TestDefinitionModalComponent } from './test-definition-modal.component';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { MatButtonModule, MatInputModule, MatRadioModule, MatDialogModule, MatIconModule, MatFormFieldModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material';\r
+import { CreateTestFormModule } from '../create-test-form/create-test-form.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatRadioModule,\r
+    MatDialogModule,\r
+    MatIconModule,\r
+    MatFormFieldModule,\r
+    CreateTestFormModule\r
+  ],\r
+  declarations: [TestDefinitionModalComponent],\r
+  exports: [TestDefinitionModalComponent],\r
+  entryComponents: [TestDefinitionModalComponent],\r
+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}]\r
+})\r
+export class TestDefinitionModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.pug b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.pug
new file mode 100644 (file)
index 0000000..13423ed
--- /dev/null
@@ -0,0 +1,25 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(style="position:relative") \r
+  .row()\r
+    .col-12\r
+      h2.pull-left(mat-dialog-title) Test Head\r
+      button.pull-right(mat-icon-button, (click)="close()")\r
+        mat-icon close\r
+  .row\r
+    mat-dialog-content(style="width: 100%")\r
+      app-create-test-head-form(*ngIf="formData", [options]="formOptions", [formData]="formData", (childEvent)="close($event)")\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.scss b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..c4523c5
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestHeadModalComponent } from './test-head-modal.component';\r
+\r
+describe('TestHeadModalComponent', () => {\r
+  let component: TestHeadModalComponent;\r
+  let fixture: ComponentFixture<TestHeadModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestHeadModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestHeadModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.ts b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.ts
new file mode 100644 (file)
index 0000000..eed1766
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject, Output, Input } from '@angular/core';\r
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';\r
+import { AppGlobals } from '../../../app.global';\r
+import { HttpClient } from '@angular/common/http';\r
+\r
+@Component({\r
+  selector: 'app-test-head-modal',\r
+  templateUrl: './test-head-modal.component.pug',\r
+  styleUrls: ['./test-head-modal.component.scss']\r
+})\r
+export class TestHeadModalComponent implements OnInit {\r
+\r
+  public formOptions;\r
+\r
+  public formData;\r
+\r
+  @Input() childEvent;\r
+\r
+  constructor(public dialogRef: MatDialogRef<TestHeadModalComponent>, private http: HttpClient,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) { }\r
+\r
+  ngOnInit() {\r
+    this.formOptions = {\r
+      goal: this.input_data.goal\r
+    };\r
+    if(this.input_data.testHead)\r
+      this.formData = this.input_data.testHead;\r
+    else\r
+      this.formData = {};\r
+  }\r
+\r
+  close(){\r
+    this.dialogRef.close();\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..fd16ca5
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestHeadModalModule } from './test-head-modal.module';\r
+\r
+describe('TestHeadModalModule', () => {\r
+  let testHeadModalModule: TestHeadModalModule;\r
+\r
+  beforeEach(() => {\r
+    testHeadModalModule = new TestHeadModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(testHeadModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.ts b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.ts
new file mode 100644 (file)
index 0000000..66a214a
--- /dev/null
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { TestHeadModalComponent } from './test-head-modal.component';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { MatButtonModule, MatInputModule, MatRadioModule, MatDialogModule, MatFormFieldModule, MatIconModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material';\r
+import { CreateTestHeadFormModule } from '../create-test-head-form/create-test-head-form.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatRadioModule,\r
+    MatDialogModule,\r
+    MatIconModule,\r
+    MatFormFieldModule,\r
+    CreateTestHeadFormModule\r
+  ],\r
+  declarations: [TestHeadModalComponent],\r
+  exports: [TestHeadModalComponent],\r
+  entryComponents: [TestHeadModalComponent],\r
+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}]\r
+})\r
+export class TestHeadModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.pug b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.pug
new file mode 100644 (file)
index 0000000..7cc39e0
--- /dev/null
@@ -0,0 +1,25 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+div(style="position:relative") \r
+  .row()\r
+    .col-12\r
+      h2.pull-left(mat-dialog-title) Test Instance\r
+      button.pull-right(mat-icon-button, (click)="close()")\r
+        mat-icon close\r
+  .row\r
+    mat-dialog-content(style="width:100%")\r
+      app-create-test-instance-form(*ngIf='!findInstance', [existingInstance] = 'editInstance', (childEvent)="close($event)")\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.scss b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..703bfcb
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { TestInstanceModalComponent } from './test-instance-modal.component';\r
+\r
+describe('TestInstanceModalComponent', () => {\r
+  let component: TestInstanceModalComponent;\r
+  let fixture: ComponentFixture<TestInstanceModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ TestInstanceModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(TestInstanceModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.ts b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.ts
new file mode 100644 (file)
index 0000000..9fb4f43
--- /dev/null
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject, Output, Input } from '@angular/core';\r
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';\r
+import { AppGlobals } from 'app/app.global';\r
+import { HttpClient } from '@angular/common/http';\r
+import { TestInstanceService } from '../../services/test-instance.service';\r
+import { TestDefinitionService } from '../../services/test-definition.service';\r
+\r
+@Component({\r
+  selector: 'app-test-instance-modal',\r
+  templateUrl: './test-instance-modal.component.pug',\r
+  styleUrls: ['./test-instance-modal.component.scss']\r
+})\r
+export class TestInstanceModalComponent implements OnInit {\r
+\r
+  @Output() editInstance;\r
+  public findInstance = true;\r
+  @Input() childEvent;\r
+\r
+\r
+  constructor(\r
+      public dialogRef: MatDialogRef<TestInstanceModalComponent>,\r
+      private http: HttpClient,\r
+      private testDefintionService: TestDefinitionService,\r
+      private testInstanceService: TestInstanceService,\r
+    @Inject(MAT_DIALOG_DATA) public inputInstanceId) { }\r
+\r
+  ngOnInit() {\r
+    if(!this.inputInstanceId){\r
+      this.findInstance = false;\r
+    }\r
+    //if the user is creating an Instance from a test definition page. Pull all data and populate testHeads\r
+    else if(this.inputInstanceId["td"]){\r
+      this.testDefintionService.get(this.inputInstanceId.td,{$populate: ['bpmnInstances.testHeads.testHeadId']}).subscribe((result) => {\r
+        this.editInstance = {\r
+          \r
+          testDefinition: result,\r
+          isEdit: false\r
+        };\r
+        \r
+        this.findInstance = false;\r
+      });\r
+\r
+    }\r
+    else if (this.inputInstanceId["ti"]) {\r
+      this.testInstanceService.get(this.inputInstanceId.ti, {$populate: ['testDefinitionId']}).subscribe((result) => {\r
+        \r
+        this.editInstance = {};\r
+        this.editInstance.testInstance = result;\r
+        if(this.inputInstanceId.isEdit){\r
+          this.editInstance.isEdit = true;\r
+        }else{\r
+          this.editInstance.isEdit = false;\r
+        }\r
+        this.findInstance = false;\r
+      });\r
+    }else{\r
+      this.findInstance = false\r
+    }\r
+  }\r
+\r
+  close() {\r
+    this.dialogRef.close();\r
+  }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..8ca1097
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestInstanceModalModule } from './test-instance-modal.module';\r
+\r
+describe('TestInstanceModalModule', () => {\r
+  let testInstanceModalModule: TestInstanceModalModule;\r
+\r
+  beforeEach(() => {\r
+    testInstanceModalModule = new TestInstanceModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(testInstanceModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.ts b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.ts
new file mode 100644 (file)
index 0000000..96be1e9
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { MatInputModule, MatDialogModule, MatFormFieldModule, MatIconModule, MatButtonModule } from '@angular/material';\r
+import { CreateTestInstanceFormModule } from '../create-test-instance-form/create-test-instance-form.module';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { TestInstanceModalComponent } from './test-instance-modal.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatInputModule,\r
+    MatDialogModule,\r
+    MatFormFieldModule,\r
+    MatButtonModule,\r
+    MatIconModule,\r
+    CreateTestInstanceFormModule\r
+    \r
+  ],\r
+  declarations: [TestInstanceModalComponent],\r
+  exports: [TestInstanceModalComponent],\r
+  entryComponents: [TestInstanceModalComponent]\r
+})\r
+export class TestInstanceModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.pug b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.pug
new file mode 100644 (file)
index 0000000..401d807
--- /dev/null
@@ -0,0 +1,44 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+h2.mb-1(mat-dialog-title) Search Users By Email\r
+//input.bg-light.form-control(mat-dialog-title, type="text", placeholder="Search...", [(ngModel)]="search.testName")\r
+mat-form-field(style="width:100%")\r
+  input(matInput, type='search', placeholder='Search email...', color='blue', [(ngModel)]='search.email')\r
+  button(mat-button, *ngIf='search.email', matSuffix, mat-icon-button, aria-label='Clear', (click)="search.email=''")\r
+    mat-icon close\r
+\r
+mat-dialog-content\r
+  .row\r
+    .col-md-8\r
+      .list-group\r
+        .px-4.py-3\r
+          .mr-1.ml-1(*ngFor="let user of users | filterBy:search")\r
+            mat-checkbox(*ngIf="search.email.length > 0", [(ngModel)]="user.isSelected", (change)="selectUser(user)") \r
+              .ml-1\r
+                h5 {{ user.firstName }} {{user.lastName}} \r
+                p.mb-0 {{ user.email }}\r
+    .col-md-4\r
+      h4(*ngIf="selectedUsers.length > 0") Selected Users\r
+      .list-group\r
+        .mr-1.ml-1(*ngFor="let user of selectedUsers")\r
+          mat-checkbox([(ngModel)] = "user.isSelected", (change)="unselectUser(user)") \r
+            .ml-1\r
+                h5 {{ user.firstName }} {{user.lastName}} \r
+                p.mb-0 {{ user.email }}\r
+mat-dialog-actions \r
+    button.bg-primary.text-white(mat-button, (click)="addUsers()") Add To Group\r
+               \r
diff --git a/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.scss b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.spec.ts b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.spec.ts
new file mode 100644 (file)
index 0000000..1a42102
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { UserSelectComponent } from './user-select.component';\r
+\r
+describe('UserSelectComponent', () => {\r
+  let component: UserSelectComponent;\r
+  let fixture: ComponentFixture<UserSelectComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ UserSelectComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(UserSelectComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.ts b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.ts
new file mode 100644 (file)
index 0000000..5cbfa0b
--- /dev/null
@@ -0,0 +1,117 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';\r
+import { UserService } from 'app/shared/services/user.service';\r
+import { GroupService } from 'app/shared/services/group.service';\r
+\r
+@Component({\r
+  selector: 'app-user-select',\r
+  templateUrl: './user-select.component.pug',\r
+  styleUrls: ['./user-select.component.scss']\r
+})\r
+export class UserSelectComponent implements OnInit {\r
+\r
+  \r
+  public data; \r
+  public users; \r
+  public group;\r
+  public search;\r
+  public selectedUsers;\r
+\r
+  constructor(\r
+    public dialogRef: MatDialogRef<UserSelectComponent>,\r
+    private userService: UserService,\r
+    private groupService: GroupService,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) {\r
+    this.data = {};\r
+   }\r
+  \r
+  onNoClick(): void {\r
+    this.dialogRef.close();\r
+  }\r
+\r
+  selectUser(user){\r
+    //this.unselectUser();\r
+    if(user.isSelected){\r
+      this.selectedUsers.push(user);\r
+    }else{\r
+      //user.isSelected = false;\r
+      this.unselectUser(user);\r
+    }\r
+    \r
+\r
+  }\r
+\r
+  unselectUser(user){\r
+    // this.selectedUsers = this.selectedUsers.filter(user => user.isSelected);\r
+    this.selectedUsers.splice(this.selectedUsers.findIndex(function(userN){ return userN._id.toString() === user._id.toString(); }), 1);\r
+    \r
+  }\r
+\r
+  addUsers(){\r
+    let usersToAdd = this.selectedUsers;\r
+\r
+    //filters users that are already in the group to avoid duplicates\r
+    if(this.group.members){\r
+      usersToAdd = this.selectedUsers.filter(user =>\r
+        this.group.members.filter(member => member.userId.toString() == user._id.toString()).length <= 0\r
+      );\r
+    }\r
+   \r
+    //populates the users roles and userId field\r
+    for(let i = 0; i < usersToAdd.length; i++){\r
+        usersToAdd[i] = {\r
+          userId : usersToAdd[i]._id,\r
+          roles : ["user"]\r
+        }\r
+    }\r
+    //sets up patch object\r
\r
+    let groupPatch = {\r
+      _id : this.input_data.groupId,\r
+      $push : { members: { $each : usersToAdd } }\r
+      \r
+    }\r
+    this.groupService.patch(groupPatch).subscribe((results) => {\r
+      this.dialogRef.close(usersToAdd);\r
+    });\r
+    \r
+  }\r
+\r
+  ngOnInit() {\r
+    this.users = [];\r
+    this.selectedUsers = [];\r
+    this.userService.find({$limit: -1, $select: ['firstName', 'lastName', 'email']})\r
+      .subscribe(\r
+        (result) => {\r
+            this.users = result;\r
+        },\r
+        (error) => {\r
+            console.log(error);\r
+      });\r
+      this.groupService.get(this.input_data.groupId).subscribe((res) => {\r
+        this.group = res;\r
+      })\r
+    \r
+    this.search = {};\r
+    this.search.email = ""; \r
+    this.input_data.testDefinition = {};\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/user-select/user-select.module.spec.ts b/otf-frontend/client/src/app/shared/modules/user-select/user-select.module.spec.ts
new file mode 100644 (file)
index 0000000..dc33dc2
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { UserSelectModule } from './user-select.module';\r
+\r
+describe('UserSelectModule', () => {\r
+  let userSelectModule: UserSelectModule;\r
+\r
+  beforeEach(() => {\r
+    userSelectModule = new UserSelectModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(userSelectModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/user-select/user-select.module.ts b/otf-frontend/client/src/app/shared/modules/user-select/user-select.module.ts
new file mode 100644 (file)
index 0000000..8793aed
--- /dev/null
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { UserSelectComponent } from './user-select.component';\r
+import { FormsModule } from '@angular/forms';\r
+import { FilterPipeModule } from 'ngx-filter-pipe';\r
+import { MatButtonModule, MatInputModule, MatDialogModule, MatIconModule, MatFormFieldModule, MatCheckboxModule } from '@angular/material';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    FilterPipeModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatDialogModule,\r
+    MatIconModule,\r
+    MatFormFieldModule,\r
+    MatCheckboxModule\r
+  ],\r
+  declarations: [UserSelectComponent],\r
+  exports: [UserSelectComponent],\r
+  entryComponents: [UserSelectComponent]\r
+})\r
+export class UserSelectModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.pug b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.pug
new file mode 100644 (file)
index 0000000..10da824
--- /dev/null
@@ -0,0 +1,32 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+.row\r
+  .col-md-6\r
+    h5 Scheduled Instance\r
+    Label {{schedule.testInstanceName}}\r
+    h5 Schedule Start Date\r
+    Label {{ schedule.data.testSchedule._testInstanceStartDate }}\r
+    h5 Schedule End Date\r
+    Label {{schedule.data.testSchedule._testInstanceEndDate}}\r
+  .col-md-6\r
+    h5 Next Run On\r
+    Label {{ schedule.nextRunAt }}\r
+    h5 Last Run On\r
+    Label {{schedule.lastRunAt}}\r
+    h5 Run Every\r
+    Label {{schedule.repeatInterval}}\r
+  button.bg-primary.text-white(mat-button, mat-dialog-close) Close
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.scss b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..0e95912
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ViewScheduleModalComponent } from './view-schedule-modal.component';\r
+\r
+describe('ViewScheduleModalComponent', () => {\r
+  let component: ViewScheduleModalComponent;\r
+  let fixture: ComponentFixture<ViewScheduleModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ViewScheduleModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ViewScheduleModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.ts b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.ts
new file mode 100644 (file)
index 0000000..c047c92
--- /dev/null
@@ -0,0 +1,45 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject } from '@angular/core';\r
+import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';\r
+\r
+@Component({\r
+  selector: 'app-view-schedule-modal',\r
+  templateUrl: './view-schedule-modal.component.pug',\r
+  styleUrls: ['./view-schedule-modal.component.scss']\r
+})\r
+export class ViewScheduleModalComponent implements OnInit  {\r
+  \r
+  public data; \r
+\r
+  constructor( \r
+    private dialogRef: MatDialogRef<ViewScheduleModalComponent>, \r
+    private dialog: MatDialog,\r
+    @Inject(MAT_DIALOG_DATA) public schedule: any\r
+  ) {\r
+    }\r
+\r
+  onNoClick(): void {\r
+    this.dialogRef.close();\r
+  }\r
+\r
+  ngOnInit() {\r
+    if(!this.schedule.data.testSchedule._testInstanceEndDate){\r
+      this.schedule.data.testSchedule._testInstanceEndDate = 'none';\r
+    }\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..cd52d9d
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ViewScheduleModalModule } from './view-schedule-modal.module';\r
+\r
+describe('ViewScheduleModalModule', () => {\r
+  let viewScheduleModalModule: ViewScheduleModalModule;\r
+\r
+  beforeEach(() => {\r
+    viewScheduleModalModule = new ViewScheduleModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(viewScheduleModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.ts b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.ts
new file mode 100644 (file)
index 0000000..dbb706e
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { MatDialogModule, MatButtonModule } from '@angular/material';\r
+import { ViewScheduleModalComponent } from './view-schedule-modal.component';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    MatDialogModule,\r
+    MatButtonModule\r
+  ],\r
+  declarations: [ViewScheduleModalComponent],\r
+  exports: [ViewScheduleModalComponent],\r
+  entryComponents: [ViewScheduleModalComponent]\r
+})\r
+export class ViewScheduleModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.pug b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.pug
new file mode 100644 (file)
index 0000000..7c33d5e
--- /dev/null
@@ -0,0 +1,23 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+h2.mat-dialog-title\r
+  span(*ngIf="td") {{ td.testName }} \r
+  | Workflow\r
+mat-dialog-content\r
+  #canvas(style="width: 100%; height: 100%")\r
+mat-dialog-actions.pull-right\r
+  button(mat-raised-button, mat-dialog-close, color="primary") Close
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.scss b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.scss
new file mode 100644 (file)
index 0000000..90560e3
--- /dev/null
@@ -0,0 +1,20 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+mat-dialog-content {\r
+    max-height: none;\r
+    height: calc(100% - 90px);\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.spec.ts b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..e11459c
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { ViewWorkflowModalComponent } from './view-workflow-modal.component';\r
+\r
+describe('ViewWorkflowModalComponent', () => {\r
+  let component: ViewWorkflowModalComponent;\r
+  let fixture: ComponentFixture<ViewWorkflowModalComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ ViewWorkflowModalComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(ViewWorkflowModalComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.ts b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.ts
new file mode 100644 (file)
index 0000000..7cef8dc
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Inject, HostListener } from '@angular/core';\r
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';\r
+import { TestHeadModalComponent } from '../test-head-modal/test-head-modal.component';\r
+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';\r
+\r
+\r
+@Component({\r
+  selector: 'app-view-workflow-modal',\r
+  templateUrl: './view-workflow-modal.component.pug',\r
+  styleUrls: ['./view-workflow-modal.component.scss']\r
+})\r
+export class ViewWorkflowModalComponent implements OnInit {\r
+\r
+  public viewer;\r
+\r
+  constructor(\r
+    public dialogRef: MatDialogRef<TestHeadModalComponent>, \r
+    private bpmnFactory: BpmnFactoryService,\r
+    @Inject(MAT_DIALOG_DATA) public input_data\r
+  ) { }\r
+\r
+  async ngOnInit() {\r
+\r
+    this.viewer = await this.bpmnFactory.setup({\r
+      mode: 'viewer',\r
+      options: {\r
+        container: '#canvas'\r
+      },\r
+      xml: this.input_data.xml,\r
+      fileId: this.input_data.fileId,\r
+      testDefinitionId: this.input_data.testDefinitionId,\r
+      version: this.input_data.version\r
+    });\r
+\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.spec.ts b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.spec.ts
new file mode 100644 (file)
index 0000000..a1da0a8
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { ViewWorkflowModalModule } from './view-workflow-modal.module';\r
+\r
+describe('ViewWorkflowModalModule', () => {\r
+  let viewWorkflowModalModule: ViewWorkflowModalModule;\r
+\r
+  beforeEach(() => {\r
+    viewWorkflowModalModule = new ViewWorkflowModalModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(viewWorkflowModalModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.ts b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.ts
new file mode 100644 (file)
index 0000000..5538574
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { ViewWorkflowModalComponent } from './view-workflow-modal.component';\r
+import { MAT_DIALOG_DEFAULT_OPTIONS, MatDialogModule, MatButtonModule } from '@angular/material';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    MatDialogModule,\r
+    MatButtonModule\r
+  ],\r
+  declarations: [ViewWorkflowModalComponent],\r
+  exports: [ViewWorkflowModalComponent],\r
+  entryComponents: [ViewWorkflowModalComponent],\r
+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}]\r
+})\r
+export class ViewWorkflowModalModule { }\r
diff --git a/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.pug b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.pug
new file mode 100644 (file)
index 0000000..052fbfb
--- /dev/null
@@ -0,0 +1,36 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+//-                                                                             #\r
+//-  Licensed under the Apache License, Version 2.0 (the "License");            #\r
+//-  you may not use this file except in compliance with the License.           #\r
+//-  You may obtain a copy of the License at                                    #\r
+//-                                                                             #\r
+//-      http://www.apache.org/licenses/LICENSE-2.0                             #\r
+//-                                                                             #\r
+//-  Unless required by applicable law or agreed to in writing, software        #\r
+//-  distributed under the License is distributed on an "AS IS" BASIS,          #\r
+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+//-  See the License for the specific language governing permissions and        #\r
+//-  limitations under the License.                                             #\r
+//- #############################################################################\r
+\r
+\r
+form(style="width:100%")\r
+  .row\r
+    .col-sm-6\r
+      .row\r
+        mat-form-field.mr-2\r
+          mat-select((selectionChange)="onFormChange()", name="ns", placeholder="Async", [(value)]="workReq.async", required)\r
+            mat-option([value]="false") False\r
+            mat-option([value]="true") True\r
+      .row\r
+        mat-form-field.mr-2(*ngIf="workReq")\r
+          input(matInput, (onChange)="onFormChange()", type="text", name="asyncTopic", placeholder="Async Topic", [(ngModel)]="workReq.asyncTopic")\r
+      \r
+    .col-sm-6\r
+      .row\r
+        mat-form-field.mr-2\r
+          input(matInput, (onChange)="onFormChange()", type="text", name="testInstanceId", placeholder="Test Instance Id", [(ngModel)]="workReq.testInstanceId", required)\r
+      .row\r
+        mat-form-field\r
+          input(matInput, (onChange)="onFormChange()", type="number", name="timeoutTime", placeholder="Timeout Time in Millis", [(ngModel)]="workReq.maxExecutionTimeInMillis", required)\r
+        
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.scss b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.scss
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.spec.ts b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.spec.ts
new file mode 100644 (file)
index 0000000..d789ffb
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';\r
+\r
+import { WorkflowRequestComponent } from './workflow-request.component';\r
+\r
+describe('WorkflowRequestComponent', () => {\r
+  let component: WorkflowRequestComponent;\r
+  let fixture: ComponentFixture<WorkflowRequestComponent>;\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      declarations: [ WorkflowRequestComponent ]\r
+    })\r
+    .compileComponents();\r
+  }));\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(WorkflowRequestComponent);\r
+    component = fixture.componentInstance;\r
+    fixture.detectChanges();\r
+  });\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.ts b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.ts
new file mode 100644 (file)
index 0000000..35f5042
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, EventEmitter, Input, Output } from '@angular/core';\r
+import { FormGroup } from '@angular/forms';\r
+\r
+@Component({\r
+  selector: 'app-workflow-request',\r
+  templateUrl: './workflow-request.component.pug',\r
+  styleUrls: ['./workflow-request.component.scss']\r
+})\r
+\r
+export class WorkflowRequestComponent implements OnInit {\r
+  @Input() public formData;\r
+  @Input() public taskId;\r
+  @Input() public index;\r
+\r
+  @Output() public childEvent = new EventEmitter();\r
+\r
+  public workReq;\r
+  constructor() { }\r
+\r
+  ngOnInit() {\r
+    this.workReq = this.formData;\r
+  }\r
+\r
+  onFormChange(){  \r
+    let event = {\r
+      object: this.workReq,\r
+      taskId: this.taskId,\r
+      index: this.index\r
+    };\r
+    this.childEvent.emit(event);\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.spec.ts b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.spec.ts
new file mode 100644 (file)
index 0000000..e6d1f84
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { WorkflowRequestModule } from './workflow-request.module';\r
+\r
+describe('WorkflowRequestModule', () => {\r
+  let workflowRequestModule: WorkflowRequestModule;\r
+\r
+  beforeEach(() => {\r
+    workflowRequestModule = new WorkflowRequestModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(workflowRequestModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.ts b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.ts
new file mode 100644 (file)
index 0000000..ca959d9
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { WorkflowRequestComponent } from './workflow-request.component';\r
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';\r
+import { MatButtonModule, MatInputModule, MatSelectModule, MatOptionModule, MatSnackBarModule, MatIconModule, MatDialogModule } from '@angular/material';\r
+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    MatButtonModule,\r
+    MatInputModule,\r
+    MatSelectModule,\r
+    MatOptionModule,\r
+    MatSnackBarModule,\r
+    AlertSnackbarModule,\r
+    MatIconModule,\r
+    ReactiveFormsModule,\r
+    MatDialogModule\r
+  ],\r
+  declarations: [WorkflowRequestComponent],\r
+  exports: [WorkflowRequestComponent]\r
+})\r
+export class WorkflowRequestModule { }\r
diff --git a/otf-frontend/client/src/app/shared/pipes/shared-pipes.module.ts b/otf-frontend/client/src/app/shared/pipes/shared-pipes.module.ts
new file mode 100644 (file)
index 0000000..c582d92
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+\r
+@NgModule({\r
+    imports: [\r
+        CommonModule\r
+    ],\r
+    declarations: [\r
+   \r
+    ],\r
+    exports: [\r
+   \r
+    ]\r
+})\r
+export class SharedPipesModule { }\r
diff --git a/otf-frontend/client/src/app/shared/services/account.service.spec.ts b/otf-frontend/client/src/app/shared/services/account.service.spec.ts
new file mode 100644 (file)
index 0000000..2485439
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { AccountService } from './account.service';\r
+\r
+describe('AccountService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: AccountService = TestBed.get(AccountService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/account.service.ts b/otf-frontend/client/src/app/shared/services/account.service.ts
new file mode 100644 (file)
index 0000000..cbbb1d3
--- /dev/null
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { HttpClient, HttpHeaders } from '@angular/common/http';\r
+import { Observable } from 'rxjs';\r
+import { AppGlobals } from '../../app.global';\r
+import { map } from 'rxjs/operators';\r
+\r
+const httpOptions = {\r
+    headers: new HttpHeaders({ 'Content-Type': 'application/json' })\r
+};\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class AccountService {\r
+\r
+    constructor(private cookie: CookieService, private http: HttpClient) { }\r
+\r
+\r
+    verify(token): Observable<Object>{\r
+        let body = {\r
+            action: 'verifySignupLong',\r
+            value: token\r
+        };\r
+\r
+        return this.http.post(AppGlobals.baseAPIUrl + 'authManagement', body, httpOptions)\r
+\r
+    }\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/auth.service.spec.ts b/otf-frontend/client/src/app/shared/services/auth.service.spec.ts
new file mode 100644 (file)
index 0000000..91de3d6
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { AuthService } from './auth.service';\r
+\r
+describe('AuthService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: AuthService = TestBed.get(AuthService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/auth.service.ts b/otf-frontend/client/src/app/shared/services/auth.service.ts
new file mode 100644 (file)
index 0000000..7b5fe3f
--- /dev/null
@@ -0,0 +1,85 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { HttpClient, HttpHeaders } from '@angular/common/http';\r
+import { Observable } from 'rxjs';\r
+import { AppGlobals } from '../../app.global';\r
+import { map } from 'rxjs/operators';\r
+import { FeathersService } from './feathers.service';\r
+\r
+const httpOptions = {\r
+  headers: new HttpHeaders({ 'Content-Type': 'application/json' })\r
+};\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class AuthService {\r
+\r
+  constructor(private cookie: CookieService, private http: HttpClient, private feathers: FeathersService) { }\r
+\r
+  //logs user into the app and store the auth token in cookie\r
+  login(userLogin): Observable<Object> {\r
+    let body = userLogin;\r
+    body.strategy = "local";\r
+    return new Observable(observer => {\r
+      this.feathers.authenticate(body)\r
+        .subscribe(res => {\r
+          this.storeUser(res);\r
+          observer.next(res);\r
+        },\r
+        err => {\r
+          observer.error(err);\r
+        });\r
+    });\r
+    // return this.http.post(AppGlobals.baseAPIUrl + 'authentication', body, httpOptions)\r
+    //   .pipe(map(authResult => {\r
+    //     if (authResult && authResult['accessToken']) {\r
+    //       this.storeUser(authResult);\r
+    //     }\r
+    //     return authResult;\r
+    //   }));\r
+  }\r
+\r
+  register(user): Observable<Object> {\r
+    return this.http.post(AppGlobals.baseAPIUrl + 'users', user, httpOptions);\r
+  }\r
+\r
+  //logs user out of app\r
+  logout() {\r
+    this.feathers.logout();\r
+    window.localStorage.clear();\r
+    this.cookie.delete('access_token');\r
+    this.cookie.delete('currentUser');\r
+  }\r
+\r
+  //store a user\r
+  storeUser(user) {\r
+\r
+    if (user.accessToken) {\r
+      window.localStorage.setItem('access_token', user['accessToken'])\r
+      window.localStorage.setItem('user_rules', JSON.stringify(user['user']['rules']));\r
+\r
+      //The rules are too large to store as a cookie\r
+      delete user['user']['rules'];\r
+\r
+      this.cookie.set('access_token', JSON.stringify(user['accessToken']));\r
+      this.cookie.set('currentUser', JSON.stringify(user['user']));\r
+    }\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/execute.service.ts b/otf-frontend/client/src/app/shared/services/execute.service.ts
new file mode 100644 (file)
index 0000000..66887b4
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from "@angular/core";\r
+import { ModelService } from "./model.service";\r
+import { HttpClient } from "@angular/common/http";\r
+import { ParamsService } from "./params.service";\r
+import { CookieService } from "ngx-cookie-service";\r
+import { FeathersService } from "./feathers.service";\r
+import { Observable } from "rxjs";\r
+\r
+@Injectable({\r
+    providedIn: 'root'\r
+})\r
+\r
+export class ExecuteService extends ModelService {\r
+    constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService) {\r
+        super('execute', http, Params, cookie, feathers);\r
+    }\r
+\r
+    create(data, params?): Observable<Object> {\r
+        data.asyncTopic = "";\r
+        return super.create(data, params);\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/services/feathers.service.ts b/otf-frontend/client/src/app/shared/services/feathers.service.ts
new file mode 100644 (file)
index 0000000..e5ba3eb
--- /dev/null
@@ -0,0 +1,88 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+    \r
+import { Injectable, OnInit } from '@angular/core';\r
+\r
+import * as feathers from '@feathersjs/client';\r
+import * as io from 'socket.io-client';\r
+import * as socketio from '@feathersjs/socketio-client';\r
+import * as authentication from '@feathersjs/authentication-client';\r
+import { Observable, from, interval } from 'rxjs';\r
+import { now } from 'moment';\r
+import { AppGlobals } from 'app/app.global';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { Router } from '@angular/router';\r
+\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class FeathersService {\r
+  // There are no proper typings available for feathers, due to its plugin-heavy nature\r
+  private _feathers: any;\r
+  public _socket: any;\r
+  public auth: Observable<Object>;\r
+  \r
+  constructor(private route: Router) {\r
+    this._socket = io('/',{\r
+      transports: ['websocket']\r
+    });       // init socket.io\r
+    this._socket.on('connect_error', function(data){\r
+      route.navigateByUrl('/login');\r
+    });\r
+    this._feathers = feathers();                      // init Feathers             // add hooks plugin\r
+    this._feathers.configure(socketio(this._socket, {\r
+        timeout: 100000000\r
+    })); // add socket.io plugin\r
+    this._feathers.configure(authentication({\r
+        storage: window.localStorage,\r
+        storageKey: 'access_token'\r
+    }));\r
+\r
+    //set observiable for services to check before calling the service\r
+    this.auth = from(this._feathers.authenticate());\r
+    \r
+  }\r
+\r
+  // expose services\r
+  public service(name: string) {\r
+    return this._feathers.service(name);\r
+  }\r
+\r
+  public socket(){\r
+    return this._socket;\r
+  }\r
+\r
+  // expose authentication\r
+  public authenticate(credentials?): Observable<Object> { \r
+    return new Observable(observer => {\r
+      this.auth = from(this._feathers.authenticate(credentials).then(res => {\r
+        observer.next(res);\r
+      }, err => {\r
+        observer.error(err);\r
+        this.route.navigate(['/login'])\r
+      }));\r
+    });\r
+\r
+  }\r
+\r
+  // expose logout\r
+  public logout() {\r
+    return this._feathers.logout();\r
+  }\r
+}\r
+\r
diff --git a/otf-frontend/client/src/app/shared/services/feedback.service.spec.ts b/otf-frontend/client/src/app/shared/services/feedback.service.spec.ts
new file mode 100644 (file)
index 0000000..7aa654b
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { FeedbackService } from './feedback.service';\r
+\r
+describe('FeedbackService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: FeedbackService = TestBed.get(FeedbackService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/feedback.service.ts b/otf-frontend/client/src/app/shared/services/feedback.service.ts
new file mode 100644 (file)
index 0000000..a42c2f9
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import {HttpClient} from "@angular/common/http";\r
+import {CookieService} from "ngx-cookie-service";\r
+import { Observable } from 'rxjs';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { FeathersService } from './feathers.service';\r
+\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class FeedbackService extends ModelService {\r
+\r
+    constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){\r
+        super('feedback', http, Params, cookie, feathers);\r
+    }\r
+\r
+    sendFeedback(msg): Observable<Object>{\r
+        let body = {\r
+            data: msg,\r
+        };\r
+        \r
+        return this.create(body);\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/file-transfer.service.spec.ts b/otf-frontend/client/src/app/shared/services/file-transfer.service.spec.ts
new file mode 100644 (file)
index 0000000..322ba32
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { FileTransferService } from './file-transfer.service';\r
+\r
+describe('FileTransferService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [FileTransferService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([FileTransferService], (service: FileTransferService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/file-transfer.service.ts b/otf-frontend/client/src/app/shared/services/file-transfer.service.ts
new file mode 100644 (file)
index 0000000..e607895
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class FileTransferService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){\r
+    super('file-transfer', http, Params, cookie, feathers);\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/file.service.spec.ts b/otf-frontend/client/src/app/shared/services/file.service.spec.ts
new file mode 100644 (file)
index 0000000..3ff1861
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { FileService } from './file.service';\r
+\r
+describe('FileService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [FileService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([FileService], (service: FileService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/file.service.ts b/otf-frontend/client/src/app/shared/services/file.service.ts
new file mode 100644 (file)
index 0000000..a46c89d
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class FileService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){\r
+    super('files', http, Params, cookie, feathers);\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/group.service.spec.ts b/otf-frontend/client/src/app/shared/services/group.service.spec.ts
new file mode 100644 (file)
index 0000000..022d977
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { GroupService } from './group.service';\r
+\r
+describe('GroupService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [GroupService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([GroupService], (service: GroupService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/group.service.ts b/otf-frontend/client/src/app/shared/services/group.service.ts
new file mode 100644 (file)
index 0000000..477ae92
--- /dev/null
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
+import { Observable, Subject } from 'rxjs';\r
+import * as organizeGroups from '../../../../../server/src/feathers/hooks/permissions/get-permissions';\r
+import { UserService } from './user.service';\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class GroupService extends ModelService {\r
+\r
+  protected groupList;\r
+  protected selectedGroup;\r
+\r
+  protected groupListChange: Subject<Array<any>> = new Subject<Array<any>>();\r
+  protected selectedGroupChange: Subject<Object> = new Subject<Object>();\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _users: UserService) {\r
+    super('groups', http, Params, cookie, feathers);\r
+    this.setUp();\r
+  }\r
+  //new edit:\r
+  public currentUser;\r
+\r
+  setUp() {\r
+    let currentId = window.localStorage.getItem('currentGroupId');\r
+\r
+    this.currentUser = JSON.parse(this.cookie.get('currentUser'));\r
+\r
+    this.find({ $limit: -1, lookup: 'both' }).subscribe(res => {\r
+      this.setGroupList(res);\r
+\r
+      if (currentId) {\r
+        this.setGroup(this.groupList.filter(elem => elem._id == currentId)[0]);\r
+      } else if (this.currentUser.defaultGroup) {\r
+        this.setGroup(this.groupList.filter(elem => elem._id == this.currentUser.defaultGroup)[0]);\r
+      }else {\r
+        //set to first group\r
+      }\r
+    },\r
+      err => {\r
+        console.log(err);\r
+      })\r
+  }\r
+\r
+\r
+\r
+  getGroup() {\r
+    return this.selectedGroup;\r
+  }\r
+\r
+  getGroupList() {\r
+    return this.groupList;\r
+  }\r
+\r
+  setGroup(group: any) {\r
+    this.selectedGroup = group;\r
+    window.localStorage.setItem('currentGroupId', group._id);\r
+    this.selectedGroupChange.next(this.selectedGroup);\r
+    if (!this.currentUser.defaultGroupEnabled) {\r
+      let userPatch = {\r
+        _id: this.currentUser._id,\r
+        defaultGroup: group._id,\r
+        defaultGroupEnabled: false\r
+      };\r
+\r
+      this._users.patch(userPatch).subscribe((res) => {\r
+        \r
+      });\r
+    }\r
+  }\r
+\r
+  organizeGroups(groups){\r
+    return organizeGroups(this.currentUser, groups);\r
+  }\r
+\r
+  setGroupList(groups) {\r
+    this.groupList = organizeGroups(this.currentUser, groups);\r
+    this.groupListChange.next(this.groupList);\r
+  }\r
+\r
+  listChange(): Subject<Array<any>> {\r
+    return this.groupListChange;\r
+  }\r
+\r
+  groupChange(): Subject<Object> {\r
+    return this.selectedGroupChange;\r
+  }\r
+\r
+  format(arr: Array<any>) {\r
+\r
+    //puts all groups in a single level array\r
+    // arr = organizeGroups(this.currentUser, arr);\r
+\r
+    var tree = [],\r
+      mappedArr = {},\r
+      arrElem,\r
+      mappedElem;\r
+\r
+    // First map the nodes of the array to an object -> create a hash table.\r
+    for (var i = 0, len = arr.length; i < len; i++) {\r
+      arrElem = arr[i];\r
+      mappedArr[arrElem._id] = arrElem;\r
+      mappedArr[arrElem._id]['children'] = [];\r
+    }\r
+\r
+\r
+    for (var _id in mappedArr) {\r
+      if (mappedArr.hasOwnProperty(_id)) {\r
+        mappedElem = mappedArr[_id];\r
+        // If the element is not at the root level, add it to its parent array of children.\r
+        if (mappedElem.parentGroupId) {\r
+          mappedArr[mappedElem['parentGroupId']]['children'].push(mappedElem);\r
+        }\r
+        // If the element is at the root level, add it to first level elements array.\r
+        else {\r
+          tree.push(mappedElem);\r
+        }\r
+      }\r
+    }\r
+    return tree;\r
+  }\r
+\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/health.service.spec.ts b/otf-frontend/client/src/app/shared/services/health.service.spec.ts
new file mode 100644 (file)
index 0000000..21a0b30
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { HealthService } from './health.service';\r
+\r
+describe('HealthService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: HealthService = TestBed.get(HealthService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/health.service.ts b/otf-frontend/client/src/app/shared/services/health.service.ts
new file mode 100644 (file)
index 0000000..3ad80e3
--- /dev/null
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { ModelService } from './model.service';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ParamsService } from './params.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
+import { Observable } from 'rxjs';\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class HealthService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){\r
+    super('health', http, Params, cookie, feathers);\r
+  }\r
+  \r
+  get(id, params?): Observable<any>{\r
+    return super.call('get', {data: id, params: params}, '/otf/api/health/v1');\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/json2html.service.ts b/otf-frontend/client/src/app/shared/services/json2html.service.ts
new file mode 100644 (file)
index 0000000..2943c05
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from "@angular/core";\r
+import { toInteger } from "@ng-bootstrap/ng-bootstrap/util/util";\r
+\r
+@Injectable({\r
+    providedIn: 'root'\r
+})\r
+\r
+export class ToHtml {\r
+    constructor() {}\r
+\r
+    convert(json:any = [{name: 'Adam', age: 23}, {name: 'Raj', age: 22}, {name: 'Justin', age: 5}], tabs = 0){\r
+        var html = '';\r
+        var tabHtml = '';\r
+        if(typeof json === 'string'){\r
+          json = JSON.parse(json);\r
+        }\r
+        for(let i = 0; i < tabs; i++){\r
+          tabHtml += '&nbsp;&nbsp;&nbsp;&nbsp;';\r
+        }\r
+        for(let key in json){\r
+          if(json.hasOwnProperty(key)){\r
+            if(typeof json[key] === "object"){\r
+              html += tabHtml + '<b><u>' + key + ':</u></b><br/>';\r
+              if(json.constructor === Array && toInteger(key) > 0){\r
+                tabs--;\r
+              }\r
+              html += this.convert(json[key], ++tabs);\r
+            }else{\r
+              html += tabHtml + '<b><u>' + key + ':</u></b>' + '<br/>';\r
+              if(typeof json[key] === 'string'){\r
+                json[key] = json[key].replace(/\\n/g, '<br/>' + tabHtml);\r
+              }\r
+              html += tabHtml + json[key] + '<br/>';\r
+              html += '<br/>';\r
+            }\r
+          }\r
+        }\r
+        return html;\r
+      }\r
+    \r
+    convertString(str){\r
+        return str.replace(/\\n/g, '<br/>');\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/services/list.service.spec.ts b/otf-frontend/client/src/app/shared/services/list.service.spec.ts
new file mode 100644 (file)
index 0000000..07c5331
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { ListService } from './list.service';\r
+\r
+describe('TestHeadListService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [ListService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([ListService], (service: ListService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/list.service.ts b/otf-frontend/client/src/app/shared/services/list.service.ts
new file mode 100644 (file)
index 0000000..aced885
--- /dev/null
@@ -0,0 +1,77 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable, OnInit } from '@angular/core';\r
+import { BehaviorSubject } from 'rxjs';\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class ListService {\r
+\r
+  listMap: {[uniqueKey: string]: {listSource: any, currentList: any} } = {};\r
+  // private listSource = new BehaviorSubject(null);\r
+  // currentList = this.listSource.asObservable();\r
+\r
+  constructor() { }\r
+\r
+  createList(key){\r
+    this.listMap[key] = {\r
+      listSource: new BehaviorSubject(null),\r
+      currentList: null\r
+    }\r
+    this.listMap[key].currentList = this.listMap[key].listSource.asObservable();\r
+    this.listMap[key].listSource.next([]);\r
+  }\r
+\r
+  changeMessage(key, message: any) {\r
+    if(!this.listMap[key])\r
+      this.createList(key);\r
+\r
+    this.listMap[key].listSource.next(message)\r
+  }\r
+\r
+  addElement(key, obj: any){\r
+    this.listMap[key].currentList.subscribe(function(value){\r
+      //console.log(value);\r
+      value.push(obj);\r
+    });\r
+  }\r
+\r
+  removeElement(key, object_field_name, id: any){\r
+    let val = 0;\r
+    this.listMap[key].currentList.subscribe(function(value){\r
+      value.forEach(function(elem, val) {\r
+        if(elem[object_field_name] == id){\r
+          value.splice(val, 1);\r
+        }\r
+      });\r
+    });\r
+    \r
+  }\r
+\r
+  updateElement(key, object_field_name, id: any, new_object){\r
+    let val = 0;\r
+    this.listMap[key].currentList.subscribe(function(value){\r
+      value.forEach(function(elem, val) {\r
+        if(elem[object_field_name] == id){\r
+          value[val] = new_object;\r
+        }\r
+      })\r
+    });\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/model.service.ts b/otf-frontend/client/src/app/shared/services/model.service.ts
new file mode 100644 (file)
index 0000000..745e109
--- /dev/null
@@ -0,0 +1,211 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { HttpClient, HttpHeaders } from "@angular/common/http";\r
+import { AppGlobals } from "../../app.global";\r
+import { ParamsService } from "./params.service";\r
+import { Observable, observable, from } from "rxjs";\r
+import { CookieService } from "ngx-cookie-service";\r
+import { FeathersService } from "./feathers.service";\r
+import { Injectable } from "@angular/core";\r
+\r
+Injectable({\r
+    providedIn: 'root'\r
+})\r
+export class ModelService {\r
+\r
+    protected path;\r
+    protected http: HttpClient;\r
+    protected Params: ParamsService;\r
+    protected cookie: CookieService;\r
+    protected feathers: FeathersService;\r
+    private authenticated: Boolean = false;\r
+\r
+    constructor(endpoint: String, http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService) {\r
+        this.http = http;\r
+        this.Params = Params;\r
+        this.path = AppGlobals.baseAPIUrl + endpoint;\r
+        this.cookie = cookie;\r
+        this.feathers = feathers;\r
+    }\r
+\r
+    checkAuth(): Observable<Object>{\r
+        return this.feathers.auth;\r
+    }\r
+\r
+    call(method, data?, path?){\r
+        if(!path){\r
+            path = this.path;\r
+        }\r
+        return new Observable(observer => {\r
+            var init = null;\r
+            if(data.params && data.params.events){\r
+                delete data.params.events;\r
+                this.feathers.service(path)\r
+                    .on('created', data => {\r
+                        if(init){\r
+                            if(init.data){\r
+                                (init.data as Array<Object>).unshift(data);\r
+                                observer.next(init);\r
+                            }else{\r
+                                (init as Array<Object>).unshift(data);\r
+                                observer.next(init);\r
+                            }\r
+                        }\r
+                    })\r
+                    .on('removed', data => {\r
+                        if(init){\r
+                            if(init.data){\r
+                                init.data = (init.data as Array<Object>).filter(item => item['_id'] != data._id);\r
+                                observer.next(init);\r
+                            }else{\r
+                                init  = (init as Array<Object>).filter(item => item['_id'] != data._id);\r
+                                observer.next(init);\r
+                            }\r
+                        }\r
+                    })\r
+                    .on('updated', data => {\r
+                        if(init){\r
+                            if(init.data){\r
+                                (init.data as Array<Object>).forEach((elem, val) => {\r
+                                    if(elem['_id'] == data._id){\r
+                                        (init.data as Array<Object>).splice(val, 1, data);\r
+                                        return;\r
+                                    }\r
+                                })\r
+                                observer.next(init);\r
+                            }else{\r
+                                (init as Array<Object>).forEach((elem, val) => {\r
+                                    if(elem['_id'] == data._id){\r
+                                        (init as Array<Object>).splice(val, 1, data);\r
+                                        return;\r
+                                    }\r
+                                })\r
+                                observer.next(init);\r
+                            }\r
+                        }\r
+                    });\r
+                \r
+            }\r
+            this.checkAuth().subscribe(res => {\r
+                if(data.data){\r
+                    \r
+                    //UPDATE & PATCH\r
+                    if(method == 'update' || method == 'patch'){\r
+                        let id = data.data._id;\r
+                        delete data.data._id;\r
+                        this.feathers.service(path)[method](id, data.data, {query: data.params}).then(result =>{\r
+                            if(!init){\r
+                                init = result;\r
+                            }\r
+                            observer.next(result)\r
+                        }).catch(err => {\r
+                            observer.error(err)}\r
+                            );\r
+                    }else{\r
+                        this.feathers.service(path)[method](data.data, {query: data.params}).then(result =>{\r
+                            if(!init){\r
+                                init = result;\r
+                            }\r
+                            observer.next(result)\r
+                        }).catch(err => {\r
+                            observer.error(err)\r
+                        });\r
+                    }\r
+                }else{\r
+                    this.feathers.service(path)[method]({query: data.params}).then(result =>{\r
+                        if(!init){\r
+                            init = result;\r
+                        }\r
+                        observer.next(result)\r
+                    }).catch(err => observer.error(err));\r
+                }\r
+\r
+            }, err => {\r
+                \r
+                this.feathers.authenticate().subscribe(res => {\r
+                    observer.next(this.call(method, data, path));\r
+                })\r
+            });\r
+        })  \r
+    }\r
+\r
+    on(event){\r
+        return new Observable(observer => {\r
+            this.feathers.service(this.path).on(event, (data) => {\r
+                observer.next(data);\r
+            });\r
+        })\r
+    }\r
+\r
+    // sfind(params = []): Observable<Object> {\r
+    //     return this.http.get(this.path + this.Params.toString(params), this.getHttpOptions());\r
+    // }\r
+\r
+    find(params?): Observable<Object> {\r
+\r
+        return this.call('find', {params: params})\r
+    }\r
+\r
+    // sget(id, params = []): Observable<Object> {\r
+    //     return from(this.http.get(this.path + '/' + id + this.Params.toString(params), this.getHttpOptions()));\r
+    // }\r
+\r
+    get(id, params?): Observable<Object> {\r
+        return this.call('get', {data: id, params: params})\r
+    }\r
+\r
+    // create(data, params = []): Observable<Object> {\r
+    //     return this.http.post(this.path + this.Params.toString(params), data, this.getHttpOptions());\r
+    // }\r
+\r
+    create(data, params?): Observable<Object> {\r
+        return this.call('create', {data: data, params: params})\r
+    }\r
+\r
+    // update(data, params = []): Observable<Object> {\r
+    //     return this.http.put(this.path + '/' + data._id + this.Params.toString(params), data, this.getHttpOptions());\r
+    // }\r
+\r
+    update(data, params?): Observable<Object> {\r
+        return this.call('update', {data: data, params: params})\r
+    }\r
+\r
+    // patch(data, params = []): Observable<Object> {\r
+    //     return this.http.patch(this.path + '/' + data._id + this.Params.toString(params), data, this.getHttpOptions());\r
+    // }\r
+\r
+    patch(data, params?): Observable<Object> {\r
+        return this.call('patch', {data: data, params: params})\r
+    }\r
+\r
+    // delete(id, params = []): Observable<Object> {\r
+    //     return this.http.delete(this.path + '/' + id + this.Params.toString(params), this.getHttpOptions());\r
+    // }\r
+\r
+    delete(id, params?): Observable<Object> {\r
+        return this.call('remove', {data: id, params: params})\r
+    }\r
+\r
+    protected getHttpOptions() {\r
+        return {\r
+            headers: new HttpHeaders({\r
+                'Authorization': 'Bearer ' + JSON.parse(this.cookie.get('access_token'))\r
+            })\r
+        };\r
+    }\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/services/params.service.ts b/otf-frontend/client/src/app/shared/services/params.service.ts
new file mode 100644 (file)
index 0000000..cbff2dd
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from "@angular/core";\r
+\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class ParamsService {\r
+    \r
+    toString(params = []) {\r
+        var string = "?";\r
+        params.forEach(elem => {\r
+            string += elem + '&&';\r
+        });\r
+        return string;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/services/scheduling.service.spec.ts b/otf-frontend/client/src/app/shared/services/scheduling.service.spec.ts
new file mode 100644 (file)
index 0000000..4c0f573
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { SchedulingService } from './scheduling.service';\r
+\r
+describe('SchedulingService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [SchedulingService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([SchedulingService], (service: SchedulingService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/scheduling.service.ts b/otf-frontend/client/src/app/shared/services/scheduling.service.ts
new file mode 100644 (file)
index 0000000..b492ea6
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { HttpClient, HttpHeaders } from "@angular/common/http";\r
+import { AppGlobals } from "../../app.global";\r
+import { ParamsService } from "./params.service";\r
+import { Observable } from "rxjs";\r
+import { Injectable } from "@angular/core";\r
+import { ModelService } from './model.service';\r
+import { CookieService } from "ngx-cookie-service";\r
+import { TestInstanceService } from "./test-instance.service";\r
+import { MatDialog } from "@angular/material";\r
+import { TestDefinitionService } from "./test-definition.service";\r
+import { ExecuteService } from "./execute.service";\r
+import { FeathersService } from "./feathers.service";\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+\r
+export class SchedulingService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private td: TestDefinitionService, private instance: TestInstanceService, private execute: ExecuteService, private dialog: MatDialog) {\r
+    super('jobs', http, Params, cookie, feathers);  \r
+  }\r
+\r
+  // create(data, params?): Observable<Object> {\r
+  //   return new Observable((observer) => {\r
+  //     this.instance.get(data.testInstanceId, { $select: ['testData'] }).subscribe(result => {\r
+  //       if(result){\r
+  //         super.create(data).subscribe(\r
+  //           res => {\r
+  //             observer.next(res);\r
+  //           },\r
+  //           err => {\r
+  //             observer.error(err);\r
+  //           }\r
+  //         )\r
+  //       }        \r
+  //     });\r
+  //   });\r
+  // }\r
+\r
+\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/services/test-definition.service.spec.ts b/otf-frontend/client/src/app/shared/services/test-definition.service.spec.ts
new file mode 100644 (file)
index 0000000..ef6f5e7
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed } from '@angular/core/testing';\r
+\r
+import { TestDefinitionService } from './test-definition.service';\r
+\r
+describe('TestDefinitionService', () => {\r
+  beforeEach(() => TestBed.configureTestingModule({}));\r
+\r
+  it('should be created', () => {\r
+    const service: TestDefinitionService = TestBed.get(TestDefinitionService);\r
+    expect(service).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/test-definition.service.ts b/otf-frontend/client/src/app/shared/services/test-definition.service.ts
new file mode 100644 (file)
index 0000000..100696d
--- /dev/null
@@ -0,0 +1,89 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { AppGlobals } from '../../app.global';\r
+import { Observable } from 'rxjs';\r
+import { ParamsService } from './params.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { ModelService } from './model.service';\r
+import { FeathersService } from './feathers.service';\r
+import { GroupService } from './group.service';\r
+\r
+\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+\r
+export class TestDefinitionService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _groups: GroupService) {\r
+    super('test-definitions', http, Params, cookie, feathers);\r
+    this.deployAll();\r
+  }\r
+\r
+  create(data, params?): Observable<Object>{\r
+    this.setGroup(data);\r
+    return super.create(data, params);\r
+  }\r
+\r
+  validate(testDefinition): Observable<Object> {\r
+    return this.call('create', {data: { testDefinition: testDefinition } }, AppGlobals.baseAPIUrl + 'bpmn-validate')\r
+    //return this.http.post(AppGlobals.baseAPIUrl + 'bpmn-validate', {testDefinition: testDefinition}, this.getHttpOptions());\r
+  }\r
+\r
+  validateSave(testDefinition): Observable<Object> {\r
+    return this.call('update', { data: {_id: null, testDefinition: testDefinition } }, AppGlobals.baseAPIUrl + 'bpmn-validate')\r
+    //return this.http.put(AppGlobals.baseAPIUrl + 'bpmn-validate', {testDefinition: testDefinition}, this.getHttpOptions());\r
+  }\r
+\r
+  check(processDefinitionKey): Observable<Object>{\r
+    return this.call('get', {data: processDefinitionKey} , AppGlobals.baseAPIUrl + 'bpmn-validate')\r
+    //return this.http.get(AppGlobals.baseAPIUrl + 'bpmn-validate/' + processDefinitionKey, this.getHttpOptions());\r
+  }\r
+\r
+  deploy(testDefinition, versionName?): Observable<Object> {\r
+    let data = {testDefinition: testDefinition};\r
+\r
+    if(versionName != null && versionName != undefined){\r
+      data['version'] = versionName;\r
+    }\r
+    return this.call('create', {data: data }, AppGlobals.baseAPIUrl + 'bpmn-upload')\r
+    //return this.http.post(AppGlobals.baseAPIUrl + 'bpmn-upload', {testDefinition: testDefinition}, this.getHttpOptions());\r
+  }\r
+\r
+  deployAll(){\r
+    // this.find({$limit: -1}).subscribe(definitions => {\r
+    //   //definitions = definitions['data'];\r
+    //   (definitions as Array<Object>).forEach((elem, val) => {\r
+    //     elem['bpmnInstances'].forEach((e , v) => {\r
+    //       let el = e;\r
+    //       this.deploy(elem, el.version).subscribe(res => {\r
+    //         console.log(res);\r
+    //       });\r
+    //     })\r
+    //   })\r
+    // })\r
+  }\r
+\r
+  private setGroup(data){\r
+    if(!data['groupId']){\r
+      data['groupId'] = this._groups.getGroup()['_id'];\r
+    }\r
+  }\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/test-execution.service.spec.ts b/otf-frontend/client/src/app/shared/services/test-execution.service.spec.ts
new file mode 100644 (file)
index 0000000..5b55bfd
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { TestExecutionService } from './test-execution.service';\r
+\r
+describe('TestExecutionService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [TestExecutionService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([TestExecutionService], (service: TestExecutionService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/test-execution.service.ts b/otf-frontend/client/src/app/shared/services/test-execution.service.ts
new file mode 100644 (file)
index 0000000..9187c9d
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { AppGlobals } from '../../app.global';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
+\r
+@Injectable({\r
+providedIn: 'root'\r
+})\r
+export class TestExecutionService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService) {\r
+    super('test-executions', http, Params, cookie, feathers);\r
+  }\r
+\r
+  status(id, params?){\r
+    return this.call('get', {data: id, params: params}, AppGlobals.baseAPIUrl + 'test-execution-status')\r
+    //return this.http.get(AppGlobals.baseAPIUrl + 'test-execution-status/' + id + this.Params.toString(params), this.getHttpOptions());\r
+  }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/shared/services/test-head.service.spec.ts b/otf-frontend/client/src/app/shared/services/test-head.service.spec.ts
new file mode 100644 (file)
index 0000000..6ed1666
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { TestHeadService } from './test-head.service';\r
+\r
+describe('TestHeadService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [TestHeadService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([TestHeadService], (service: TestHeadService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/test-head.service.ts b/otf-frontend/client/src/app/shared/services/test-head.service.ts
new file mode 100644 (file)
index 0000000..ad07560
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
+import { Observable } from 'rxjs';\r
+import { GroupService } from './group.service';\r
\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class TestHeadService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _groups: GroupService){\r
+    super('test-heads', http, Params, cookie, feathers);\r
+  }\r
+\r
+  create(data, params?): Observable<Object> {\r
+    data['groupId'] = this._groups.getGroup()['_id'];\r
+    return super.create(data, params);\r
+  }\r
+\r
+}\r
diff --git a/otf-frontend/client/src/app/shared/services/test-instance.service.spec.ts b/otf-frontend/client/src/app/shared/services/test-instance.service.spec.ts
new file mode 100644 (file)
index 0000000..c1319cc
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { TestInstanceService } from './test-instance.service';\r
+\r
+describe('TestInstanceService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [TestInstanceService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([TestInstanceService], (service: TestInstanceService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/test-instance.service.ts b/otf-frontend/client/src/app/shared/services/test-instance.service.ts
new file mode 100644 (file)
index 0000000..02b82ba
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { ParamsService } from './params.service';\r
+import { ModelService } from './model.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
+import { Observable } from 'rxjs';\r
+import { GroupService } from './group.service';\r
+\r
+@Injectable({\r
+providedIn: 'root'\r
+})\r
+export class TestInstanceService extends ModelService {\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _groups: GroupService){\r
+    super('test-instances', http, Params, cookie, feathers);\r
+  }\r
+\r
+  create(data, params?):Observable<Object>{\r
+    data['groupId'] = this._groups.getGroup()['_id'];\r
+    return super.create(data, params);\r
+  }\r
+}\r
+\r
+\r
diff --git a/otf-frontend/client/src/app/shared/services/user.service.spec.ts b/otf-frontend/client/src/app/shared/services/user.service.spec.ts
new file mode 100644 (file)
index 0000000..a7662e7
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { TestBed, inject } from '@angular/core/testing';\r
+\r
+import { UserService } from './user.service';\r
+\r
+describe('UserService', () => {\r
+  beforeEach(() => {\r
+    TestBed.configureTestingModule({\r
+      providers: [UserService]\r
+    });\r
+  });\r
+\r
+  it('should be created', inject([UserService], (service: UserService) => {\r
+    expect(service).toBeTruthy();\r
+  }));\r
+});\r
diff --git a/otf-frontend/client/src/app/shared/services/user.service.ts b/otf-frontend/client/src/app/shared/services/user.service.ts
new file mode 100644 (file)
index 0000000..0209ab5
--- /dev/null
@@ -0,0 +1,75 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Injectable } from '@angular/core';\r
+import { HttpClient } from '@angular/common/http';\r
+import { map } from 'rxjs/operators';\r
+import { ModelService } from './model.service';\r
+import { ParamsService } from './params.service';\r
+import { CookieService } from 'ngx-cookie-service';\r
+import { FeathersService } from './feathers.service';\r
+import { Ability } from '@casl/ability';\r
+\r
+\r
+@Injectable({\r
+  providedIn: 'root'\r
+})\r
+export class UserService extends ModelService {\r
+\r
+  public ability: Ability;\r
+\r
+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, private c: CookieService, feathers: FeathersService){\r
+    super('users', http, Params, cookie, feathers);\r
+    this.ability = new Ability(JSON.parse(localStorage.getItem('user_rules')));\r
+  }\r
+\r
+  getId(){\r
+    return JSON.parse(this.cookie.get('currentUser'))._id;\r
+  }\r
+\r
+  // addFavorite(ref: string, id: string){\r
+  //   return this.get(this.getId()).pipe(map(\r
+  //     result => {\r
+  //       if(!result['favorites']){\r
+  //         result['favorites'] = {};\r
+  //       }\r
+  //       if(!result['favorites'][ref]){\r
+  //         result['favorites'][ref] = [];\r
+  //       }\r
+  //       result['favorites'][ref].push(id);\r
+  //       result['favorites'][ref] = Array.from(new Set(result['favorites'][ref]));\r
+  //       this.patch(result).subscribe();\r
+  //     }\r
+  //   ));\r
+  // }\r
+\r
+  // removeFavorite(ref: string, id: string){\r
+  //   return this.get(this.getId()).pipe(map(\r
+  //     result => {\r
+  //       result['favorites'][ref].splice( result['favorites'][ref].indexOf(id), 1 );\r
+  //       this.patch(result).subscribe();\r
+  //     }\r
+  //   ));\r
+  // }\r
+\r
+  enableUser(id: string, enabled: boolean){\r
+      return this.patch({\r
+          "_id" : id,\r
+          "enabled": enabled\r
+      })\r
+\r
+  }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/app/signup/signup-routing.module.ts b/otf-frontend/client/src/app/signup/signup-routing.module.ts
new file mode 100644 (file)
index 0000000..50882e0
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { Routes, RouterModule } from '@angular/router';\r
+import { SignupComponent } from './signup.component';\r
+\r
+const routes: Routes = [\r
+    {\r
+        path: '', component: SignupComponent\r
+    }\r
+];\r
+\r
+@NgModule({\r
+    imports: [RouterModule.forChild(routes)],\r
+    exports: [RouterModule]\r
+})\r
+export class SignupRoutingModule {\r
+}\r
diff --git a/otf-frontend/client/src/app/signup/signup.component.html b/otf-frontend/client/src/app/signup/signup.component.html
new file mode 100644 (file)
index 0000000..a21c72e
--- /dev/null
@@ -0,0 +1,68 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<div class="login-page" [@routerTransition]>\r
+    <div class="row justify-content-md-center">\r
+        <div class="col-md-4">\r
+            <img class="user-avatar" src="assets/images/NetworkLogo.jpg" width="150px" />\r
+            <h1>Open Test Framework</h1>\r
+            <form role="form">\r
+                <div class="form-content">\r
+                    <div class="row justify-content-md-center">\r
+                        <div class="col-md-6">\r
+                            <div class="form-group">\r
+                                <input type="text" required [(ngModel)]="user.firstName" name="firstName" class="form-control input-underline input-lg" id="" placeholder="First Name">\r
+                            </div>\r
+\r
+                            <div class="form-group">\r
+                                <input type="text" required [(ngModel)]="user.lastName" name="lastName" class="form-control input-underline input-lg" id="" placeholder="Last Name">\r
+                            </div>\r
+\r
+                            <div class="form-group">\r
+                                <input type="email" required [(ngModel)]="user.email" name="email" class="form-control input-underline input-lg" id="" #email="ngModel" placeholder="Email">\r
+                            </div>\r
+                            <div *ngIf="email.invalid && (email.dirty || email.touched)"\r
+                                class="alert-danger">\r
+                                <div *ngIf="email.errors.required">\r
+                                    Email is required.\r
+                                </div>\r
+                            </div>\r
+                        </div>\r
+                        <div class="col-md-6">\r
+\r
+                            <div class="form-group">\r
+                                <input type="password" required minlength="8" [(ngModel)]="user.password" name="password" class="form-control input-underline input-lg" id="password1" #password="ngModel" placeholder="Password">\r
+                            </div>\r
+                            <div *ngIf="password.invalid && (password.dirty || password.touched)"\r
+                                class="alert-danger">\r
+                            <div *ngIf="password.errors.required">\r
+                                Password is required.\r
+                            </div>\r
+                            <div *ngIf="password.errors.minlength">\r
+                                Password must be at least 8 characters long.\r
+                            </div>\r
+                            </div>\r
+                            <div class="form-group">\r
+                                <input type="password" required [(ngModel)]="passwordConfirm" name="passwordConfirm" class="form-control input-underline input-lg" id="password2" placeholder="Repeat Password">\r
+                            </div>\r
+                        </div>\r
+                    </div>\r
+                </div>\r
+                <a class="btn rounded-btn" (click)='register()'> Register </a>&nbsp;\r
+            </form>\r
+        </div>\r
+    </div>\r
+</div>\r
diff --git a/otf-frontend/client/src/app/signup/signup.component.scss b/otf-frontend/client/src/app/signup/signup.component.scss
new file mode 100644 (file)
index 0000000..faabd34
--- /dev/null
@@ -0,0 +1,18 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// shared css for the login and signup page\r
+@import "../login/login.component.scss";\r
diff --git a/otf-frontend/client/src/app/signup/signup.component.spec.ts b/otf-frontend/client/src/app/signup/signup.component.spec.ts
new file mode 100644 (file)
index 0000000..e64435d
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'\r
+import { RouterTestingModule } from '@angular/router/testing'\r
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations'\r
+\r
+import { SignupComponent } from './signup.component'\r
+import { SignupModule } from './signup.module'\r
+\r
+describe('SignupComponent', () => {\r
+  let component: SignupComponent\r
+  let fixture: ComponentFixture<SignupComponent>\r
+\r
+  beforeEach(async(() => {\r
+    TestBed.configureTestingModule({\r
+      imports: [\r
+        SignupModule,\r
+        RouterTestingModule,\r
+        BrowserAnimationsModule,\r
+      ],\r
+    })\r
+    .compileComponents()\r
+  }))\r
+\r
+  beforeEach(() => {\r
+    fixture = TestBed.createComponent(SignupComponent)\r
+    component = fixture.componentInstance\r
+    fixture.detectChanges()\r
+  })\r
+\r
+  it('should create', () => {\r
+    expect(component).toBeTruthy()\r
+  })\r
+})\r
diff --git a/otf-frontend/client/src/app/signup/signup.component.ts b/otf-frontend/client/src/app/signup/signup.component.ts
new file mode 100644 (file)
index 0000000..85893e9
--- /dev/null
@@ -0,0 +1,102 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit } from '@angular/core';\r
+import { routerTransition } from '../router.animations';\r
+import { HttpClient } from '@angular/common/http';\r
+import { AppGlobals } from '../app.global';\r
+import { UserService } from '../shared/services/user.service';\r
+import { Router } from '@angular/router';\r
+import { User } from '../shared/models/user.model';\r
+import { MatDialog } from '@angular/material';\r
+import { AlertModalComponent } from '../shared/modules/alert-modal/alert-modal.component';\r
+import { AuthService } from '../shared/services/auth.service';\r
+\r
+\r
+@Component({\r
+    selector: 'app-signup',\r
+    templateUrl: './signup.component.html',\r
+    styleUrls: ['./signup.component.scss'],\r
+    animations: [routerTransition()]\r
+})\r
+export class SignupComponent implements OnInit {\r
+    public user = {\r
+        password: null,\r
+        firstName: null,\r
+        lastName: null,\r
+        email: null\r
+    };\r
+    public passwordConfirm;\r
+\r
+    constructor(public router: Router,\r
+        private auth: AuthService,\r
+        public dialog: MatDialog\r
+        ) {\r
+           \r
+        }\r
+\r
+    ngOnInit() {\r
+        \r
+    }\r
+\r
+    register(){\r
+        // let body  = {\r
+        //     firstName: this.user.firstName,\r
+        //     lastName: this.user.lastName,\r
+        //     email: this.user.email,\r
+        //     password: this.user.password\r
+        // };\r
+        \r
+        if(this.user.password != this.passwordConfirm){\r
+            const dialogRef = this.dialog.open(AlertModalComponent, {\r
+                data:{\r
+                    type: "Alert",\r
+                    message: "Passwords must match!"\r
+                }\r
+\r
+            });\r
+            \r
+            return;\r
+        }\r
+\r
+        this.auth.register(this.user) \r
+            .subscribe(\r
+                (res) => {\r
+                    const r = this.dialog.open(AlertModalComponent, {\r
+                        data: {\r
+                            type: "Alert",\r
+                            message: "Check your email to verify your account."\r
+                        }\r
+                    });\r
+\r
+                    r.afterClosed().subscribe(res => {\r
+                        this.router.navigateByUrl('/login');\r
+                    })\r
+                      \r
+                },\r
+                (err) => {\r
+                    this.dialog.open(AlertModalComponent, {\r
+                        data:{\r
+                            type: "Alert",\r
+                            message: err\r
+                        }\r
+                    });\r
+                }\r
+            );\r
+        \r
+        \r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/app/signup/signup.module.spec.ts b/otf-frontend/client/src/app/signup/signup.module.spec.ts
new file mode 100644 (file)
index 0000000..9b532fd
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { SignupModule } from './signup.module';\r
+\r
+describe('SignupModule', () => {\r
+  let signupModule: SignupModule;\r
+\r
+  beforeEach(() => {\r
+    signupModule = new SignupModule();\r
+  });\r
+\r
+  it('should create an instance', () => {\r
+    expect(signupModule).toBeTruthy();\r
+  });\r
+});\r
diff --git a/otf-frontend/client/src/app/signup/signup.module.ts b/otf-frontend/client/src/app/signup/signup.module.ts
new file mode 100644 (file)
index 0000000..a9d2ffa
--- /dev/null
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { NgModule } from '@angular/core';\r
+import { CommonModule } from '@angular/common';\r
+import { FormsModule } from '@angular/forms';\r
+import { SignupRoutingModule } from './signup-routing.module';\r
+import { SignupComponent } from './signup.component';\r
+import { HttpClientModule } from '@angular/common/http';\r
+import { AppGlobals } from '../app.global';\r
+import { MatDialogModule, MatButtonModule } from '@angular/material';\r
+import { AlertModalModule } from '../shared/modules/alert-modal/alert-modal.module';\r
+\r
+\r
+\r
+@NgModule({\r
+  imports: [\r
+    CommonModule,\r
+    FormsModule,\r
+    MatDialogModule,\r
+    MatButtonModule,\r
+    SignupRoutingModule,\r
+    AlertModalModule\r
+  ],\r
+  declarations: [SignupComponent]\r
+})\r
+export class SignupModule { }\r
diff --git a/otf-frontend/client/src/assets/fakedata.json b/otf-frontend/client/src/assets/fakedata.json
new file mode 100644 (file)
index 0000000..80494b9
--- /dev/null
@@ -0,0 +1,115 @@
+{\r
+    "test_heads": [\r
+        {\r
+            "test_head_id": 1,\r
+            "test_head_name": "test1",\r
+            "description": "Ping Test 1"\r
+        },\r
+        {\r
+            "test_head_id": 2,\r
+            "test_head_name": "test2",\r
+            "description": "Ping Test 2"\r
+        },\r
+        {\r
+            "test_head_id": 3,\r
+            "test_head_name": "test3",\r
+            "description": "Ping Test 3"\r
+        },\r
+        {\r
+            "test_head_id": 4,\r
+            "test_head_name": "test4",\r
+            "description": "Ping Test 4"\r
+        },\r
+        {\r
+            "test_head_id": 5,\r
+            "test_head_name": "test5",\r
+            "description": "Ping Test 5"\r
+        }\r
+    ],\r
+    "test_strategies": [\r
+        {\r
+            "test_strategy_id": 1,\r
+            "test_strategy_name": "strategy1",\r
+            "description": "Recursive Test 1",\r
+            "vth_nodes": [\r
+                "node1",\r
+                "node2"\r
+            ]\r
+        },\r
+        {\r
+            "test_strategy_id": 2,\r
+            "test_strategy_name": "strategy2",\r
+            "description": "Recursive Test 2",\r
+            "vth_nodes": [\r
+                "node1"\r
+            ]\r
+        },\r
+        {\r
+            "test_strategy_id": 3,\r
+            "test_strategy_name": "strategy3",\r
+            "description": "Recursive Test 3",\r
+            "vth_nodes": [\r
+                "node1",\r
+                "node2",\r
+                "node3", \r
+                "node4"\r
+            ]\r
+        }\r
+    ],\r
+    "tests": [\r
+        {\r
+            "test_id": 1,\r
+            "test_name": "Test 1",\r
+            "creator": 1,\r
+            "vts": 3,\r
+            "vth_list": {\r
+                "node1": 1,\r
+                "node2": 2,\r
+                "node3": 3,\r
+                "node4": 5\r
+            },\r
+            "vts_list": [\r
+\r
+            ]\r
+        },\r
+        {\r
+            "test_id": 2,\r
+            "test_name": "Test 2",\r
+            "creator": 2,\r
+            "vts": 2,\r
+            "vth_list": {\r
+                "node1": 3\r
+            },\r
+            "vts_list": [\r
+\r
+            ]\r
+        },\r
+        {\r
+            "test_id": 3,\r
+            "test_name": "Test 3",\r
+            "creator": 1,\r
+            "vts": 1,\r
+            "vth_list": {\r
+                "node1": 1,\r
+                "node2": 4\r
+            },\r
+            "vts_list": [\r
+\r
+            ]\r
+        }\r
+    ],\r
+    "users": [\r
+        {\r
+            "user_id": 1,\r
+            "firstName": "Adam",\r
+            "lastName": "Ordway",\r
+            "email": "agordway@gmail.com"\r
+        },\r
+        {\r
+            "user_id": 2,\r
+            "firstName": "Justin",\r
+            "lastName": "Meilinger",\r
+            "email": "mylinger@gmail.com"\r
+        }\r
+    ]\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/assets/i18n/de.json b/otf-frontend/client/src/assets/i18n/de.json
new file mode 100644 (file)
index 0000000..1936596
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "Dashboard",\r
+    "Charts": "Graphen",\r
+    "Tables": "Tabellen",\r
+    "Forms": "Formulare",\r
+    "Bootstrap Element": "Bootstrap Element",\r
+    "Bootstrap Grid": "Bootstrap Grid",\r
+    "Component": "Komponente",\r
+    "Menu": "Menü",\r
+    "Submenu": "Submenü",\r
+    "Blank Page": "Leere Seite",\r
+    "More Theme": "Mehr Themes",\r
+    "Download Now": "Jetzt runterladen",\r
+    "Language": "Sprache",\r
+    "English": "Englisch",\r
+    "French": "Französisch",\r
+    "Urdu": "Urdu",\r
+    "Spanish": "Spanisch",\r
+    "Italian": "Italienisch",\r
+    "Farsi": "Farsi",\r
+    "German": "Deutsch",\r
+    "Simplified Chinese": "Vereinfachtes Chinesisch",\r
+    "Search" : "Suchen",\r
+    "Settings" : "Einstellungen",\r
+    "Profile" : "Profil",\r
+    "Inbox" : "Posteingang",\r
+    "Log Out" : "Ausloggen",\r
+    "Pending Task" : "Ausstehende Aufgabe",\r
+    "In queue" : "In der Warteschlange",\r
+    "Mail" : "Post",\r
+    "View All" : "Alle Anzeigen"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/en.json b/otf-frontend/client/src/assets/i18n/en.json
new file mode 100644 (file)
index 0000000..2b3776f
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "Dashboard",\r
+    "Charts": "Charts",\r
+    "Tables": "Tables",\r
+    "Forms": "Forms",\r
+    "Bootstrap Element": "Bootstrap Element",\r
+    "Bootstrap Grid": "Bootstrap Grid",\r
+    "Component": "Component",\r
+    "Menu": "Menu",\r
+    "Submenu": "Submenu",\r
+    "Blank Page": "Blank Page",\r
+    "More Theme": "More Themes",\r
+    "Download Now": "Download Now",\r
+    "Language": "Language",\r
+    "English": "English",\r
+    "French": "French",\r
+    "Urdu": "Urdu",\r
+    "Spanish": "Spanish",\r
+    "Italian": "Italian",\r
+    "Farsi": "Farsi",\r
+    "German": "German",\r
+    "Simplified Chinese": "Simplified Chinese",\r
+    "Search" : "Search",\r
+    "Settings" : "Settings",\r
+    "Profile" : "Profile",\r
+    "Inbox" : "Inbox",\r
+    "Log Out" : "Log Out",\r
+    "Pending Task" : "Pending Task",\r
+    "In queue" : "In queue",\r
+    "Mail" : "Mail",\r
+    "View All" : "View All"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/es.json b/otf-frontend/client/src/assets/i18n/es.json
new file mode 100644 (file)
index 0000000..a225b5f
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "Principal",\r
+    "Charts": "Caracteres",\r
+    "Tables": "Tablas",\r
+    "Forms": "Formularios",\r
+    "Bootstrap Element": "Elementos Bootstrap",\r
+    "Bootstrap Grid": "Rejilla Bootstrap",\r
+    "Component": "Componentes",\r
+    "Menu": "Menú",\r
+    "Submenu": "Submenú",\r
+    "Blank Page": "Página en Blanco",\r
+    "More Theme": "Más temas",\r
+    "Download Now": "Descarga Ahora",\r
+    "Language": "Idioma",\r
+    "English": "Inglés",\r
+    "French": "Francés",\r
+    "Urdu": "Urdu",\r
+    "Spanish": "Español",\r
+    "Italian": "Italiano",\r
+    "Farsi": "Farsi",\r
+    "German": "Alemán",\r
+    "Simplified Chinese": "Chino simplificado",\r
+    "Search" : "Búsqueda",\r
+    "Settings" : "Ajustes",\r
+    "Profile" : "Profile",\r
+    "Inbox" : "Bandeja de entrada",\r
+    "Log Out" : "Cerrar Sesión",\r
+    "Pending Task" : "Tarea pendiente",\r
+    "In queue" : "En cola",\r
+    "Mail" : "Correo",\r
+    "View All" : "Ver todo"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/fa.json b/otf-frontend/client/src/assets/i18n/fa.json
new file mode 100644 (file)
index 0000000..508095a
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "داشبورد",\r
+    "Charts": "چارت ها",\r
+    "Tables": "جداول",\r
+    "Forms": "فرم ها",\r
+    "Bootstrap Element": "عناصر بوتسترپ",\r
+    "Bootstrap Grid": "جداول بوتسترپ",\r
+    "Component": "کامپوننت",\r
+    "Menu": "منوها",\r
+    "Submenu": "زیر منوها",\r
+    "Blank Page": "صفحه خالی",\r
+    "More Theme": "تم های بیشتر",\r
+    "Download Now": "دانلود",\r
+    "Language": "زبان",\r
+    "English": "انگلیسی",\r
+    "French": "فرانسوی",\r
+    "Urdu": "اردو",\r
+    "Spanish": "اسپانیایی",\r
+    "Italian": "ایتالیایی",\r
+    "Farsi": "فارسی",\r
+    "German": "آلمانی",\r
+    "Simplified Chinese": "چینی ساده شده",\r
+    "Search" : "جستجو",\r
+    "Settings" : "تنظیمات",\r
+    "Profile" : "مشخصات",\r
+    "Inbox" : "صندوق ورودی",\r
+    "Log Out" : "خروج از سیستم",\r
+    "Pending Task" : "وظایف در انتظار",\r
+    "In queue" : "در صف",\r
+    "Mail" : "ایمیل",\r
+    "View All" : "نمایش همه"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/fr.json b/otf-frontend/client/src/assets/i18n/fr.json
new file mode 100644 (file)
index 0000000..7e27a4f
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "Tableau de bord",\r
+    "Charts": "Hit-parade",\r
+    "Tables": "Tableaux",\r
+    "Forms": "Froms",\r
+    "Bootstrap Element": "Bootstrap Élément",\r
+    "Bootstrap Grid": "Bootstrap Grille",\r
+    "Component": "Composant",\r
+    "Menu": "Menu",\r
+    "Submenu": "Sous-menu",\r
+    "Blank Page": "Blanc Page",\r
+    "More Theme": "Plus Thèmes",\r
+    "Download Now": "Télécharger",\r
+    "Language": "Langue",\r
+    "English": "Anglais",\r
+    "French": "Français",\r
+    "Urdu": "Ourdou",\r
+    "Spanish": "Spanish",\r
+    "Italian": "Italien",\r
+    "Farsi": "Farsi",\r
+    "German": "Allemand",\r
+    "Simplified Chinese": "Chinois Simplifié",\r
+    "Search" : "Chercher",\r
+    "Settings" : "Paramètres",\r
+    "Profile" : "Profile",\r
+    "Inbox" : "Boîte de réception",\r
+    "Log Out" : "Connectez - Out",\r
+    "Pending Task" : "Tâche en attente",\r
+    "In queue" : "Dans la queue",\r
+    "Mail" : "Courrier",\r
+    "View All" : "Voir tout"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/it.json b/otf-frontend/client/src/assets/i18n/it.json
new file mode 100644 (file)
index 0000000..8ee3ae3
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "Principale",\r
+    "Charts": "Grafici",\r
+    "Tables": "Tabelle",\r
+    "Forms": "Formulari",\r
+    "Bootstrap Element": "Elementi Bootstrap",\r
+    "Bootstrap Grid": "Griglia Bootstrap",\r
+    "Component": "Componenti",\r
+    "Menu": "Menu",\r
+    "Submenu": "Submenu",\r
+    "Blank Page": "Pagina in Bianco",\r
+    "More Theme": "Altri temi",\r
+    "Download Now": "Scarica Adesso",\r
+    "Language": "Lingua",\r
+    "English": "Inglese",\r
+    "French": "Francese",\r
+    "Urdu": "Urdu",\r
+    "Spanish": "Spagnolo",\r
+    "Italian": "Italiano",\r
+    "Farsi": "Farsi",\r
+    "German": "Tedesco",\r
+    "Simplified Chinese": "Cinese semplificato",\r
+    "Search" : "Ricerca",\r
+    "Settings" : "Impostazioni",\r
+    "Profile" : "Profilo",\r
+    "Inbox" : "Posta in arrivo",\r
+    "Log Out" : "Uscire",\r
+    "Pending Task" : "Attività in sospeso",\r
+    "In queue" : "In coda",\r
+    "Mail" : "Posta",\r
+    "View All" : "Visualizza tutti"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/ur.json b/otf-frontend/client/src/assets/i18n/ur.json
new file mode 100644 (file)
index 0000000..0fd04f3
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "داشبورد",\r
+    "Charts": "چارت ها",\r
+    "Tables": "جداول",\r
+    "Forms": "فرم ها",\r
+    "Bootstrap Element": "عنصر بوتسترپ",\r
+    "Bootstrap Grid": "جدول بوتسترپ",\r
+    "Component": "کامپوننت",\r
+    "Menu": "منو",\r
+    "Submenu": "زیر منو",\r
+    "Blank Page": "صفحه خالی",\r
+    "More Theme": "تم های بیشتر",\r
+    "Download Now": "دانلود",\r
+    "Language": "زبان",\r
+    "English": "انگریزی",\r
+    "French": "فرانسیسی",\r
+    "Urdu": "اردو",\r
+    "Spanish": "ہسپانوی",\r
+    "Italian": "اطالوی",\r
+    "Farsi": "فارسی",\r
+    "German": "جرمن",\r
+    "Simplified Chinese": "چینی چینی",\r
+    "Search" : "تلاش کریں",\r
+    "Settings" : "ترتیبات",\r
+    "Profile" : "پروفائل",\r
+    "Inbox" : "ان باکس",\r
+    "Log Out" : "لاگ آوٹ",\r
+    "Pending Task" : "زیر التواء ٹاسک",\r
+    "In queue" : "قطار میں",\r
+    "Mail" : "میل",\r
+    "View All" : "سب دیکھیں"\r
+}\r
diff --git a/otf-frontend/client/src/assets/i18n/zh-CHS.json b/otf-frontend/client/src/assets/i18n/zh-CHS.json
new file mode 100644 (file)
index 0000000..ae60c29
--- /dev/null
@@ -0,0 +1,32 @@
+{\r
+    "Dashboard": "仪表板",\r
+    "Charts": "图表",\r
+    "Tables": "表格",\r
+    "Forms": "表单",\r
+    "Bootstrap Element": "Bootstrap 元素",\r
+    "Bootstrap Grid": "Bootstrap 网格",\r
+    "Component": "组件",\r
+    "Menu": "菜单",\r
+    "Submenu": "子菜单",\r
+    "Blank Page": "空白页",\r
+    "More Theme": "更多主题",\r
+    "Download Now": "现在下载",\r
+    "Language": "语言",\r
+    "English": "英语",\r
+    "French": "法语",\r
+    "Urdu": "乌尔都语",\r
+    "Spanish": "西班牙语",\r
+    "Italian": "意大利语",\r
+    "Farsi": "波斯语",\r
+    "German": "德语",\r
+    "Simplified Chinese": "简体中文",\r
+    "Search" : "搜索",\r
+    "Settings" : "设置",\r
+    "Profile" : "个人配置",\r
+    "Inbox" : "收件箱",\r
+    "Log Out" : "退出",\r
+    "Pending Task" : "挂起任务",\r
+    "In queue" : "队列中",\r
+    "Mail" : "邮件",\r
+    "View All" : "查看所有"\r
+}\r
diff --git a/otf-frontend/client/src/assets/images/404image.png b/otf-frontend/client/src/assets/images/404image.png
new file mode 100644 (file)
index 0000000..5101cc1
Binary files /dev/null and b/otf-frontend/client/src/assets/images/404image.png differ
diff --git a/otf-frontend/client/src/assets/images/NetworkLogo.jpg b/otf-frontend/client/src/assets/images/NetworkLogo.jpg
new file mode 100644 (file)
index 0000000..3c071d3
Binary files /dev/null and b/otf-frontend/client/src/assets/images/NetworkLogo.jpg differ
diff --git a/otf-frontend/client/src/assets/images/OtfIcon.png b/otf-frontend/client/src/assets/images/OtfIcon.png
new file mode 100644 (file)
index 0000000..1e61906
Binary files /dev/null and b/otf-frontend/client/src/assets/images/OtfIcon.png differ
diff --git a/otf-frontend/client/src/assets/images/equalizer.gif b/otf-frontend/client/src/assets/images/equalizer.gif
new file mode 100644 (file)
index 0000000..22ccbe3
Binary files /dev/null and b/otf-frontend/client/src/assets/images/equalizer.gif differ
diff --git a/otf-frontend/client/src/assets/images/logo.png b/otf-frontend/client/src/assets/images/logo.png
new file mode 100644 (file)
index 0000000..a931536
Binary files /dev/null and b/otf-frontend/client/src/assets/images/logo.png differ
diff --git a/otf-frontend/client/src/assets/images/networkBackground.jpg b/otf-frontend/client/src/assets/images/networkBackground.jpg
new file mode 100644 (file)
index 0000000..5b1b943
Binary files /dev/null and b/otf-frontend/client/src/assets/images/networkBackground.jpg differ
diff --git a/otf-frontend/client/src/assets/images/networkBackground1.jpg b/otf-frontend/client/src/assets/images/networkBackground1.jpg
new file mode 100644 (file)
index 0000000..fc4d343
Binary files /dev/null and b/otf-frontend/client/src/assets/images/networkBackground1.jpg differ
diff --git a/otf-frontend/client/src/assets/images/slider1.jpg b/otf-frontend/client/src/assets/images/slider1.jpg
new file mode 100644 (file)
index 0000000..9859565
Binary files /dev/null and b/otf-frontend/client/src/assets/images/slider1.jpg differ
diff --git a/otf-frontend/client/src/assets/images/slider2.jpg b/otf-frontend/client/src/assets/images/slider2.jpg
new file mode 100644 (file)
index 0000000..794d791
Binary files /dev/null and b/otf-frontend/client/src/assets/images/slider2.jpg differ
diff --git a/otf-frontend/client/src/assets/workflows/blank.bpmn b/otf-frontend/client/src/assets/workflows/blank.bpmn
new file mode 100644 (file)
index 0000000..d310b7e
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1n6f7f6" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="2.0.3">\r
+  <bpmn:process id="" isExecutable="true">\r
+    <bpmn:startEvent id="StartEvent_1" />\r
+  </bpmn:process>\r
+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">\r
+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="">\r
+      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">\r
+        <dc:Bounds x="173" y="102" width="36" height="36" />\r
+      </bpmndi:BPMNShape>\r
+    </bpmndi:BPMNPlane>\r
+  </bpmndi:BPMNDiagram>\r
+</bpmn:definitions>\r
diff --git a/otf-frontend/client/src/environments/environment.prod.ts b/otf-frontend/client/src/environments/environment.prod.ts
new file mode 100644 (file)
index 0000000..fe1834b
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+export const environment = {\r
+  production: true\r
+};\r
diff --git a/otf-frontend/client/src/environments/environment.ts b/otf-frontend/client/src/environments/environment.ts
new file mode 100644 (file)
index 0000000..98eb372
--- /dev/null
@@ -0,0 +1,24 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// The file contents for the current environment will overwrite these during build.\r
+// The build system defaults to the dev environment which uses `environment.ts`, but if you do\r
+// `ng build --env=prod` then `environment.prod.ts` will be used instead.\r
+// The list of which env maps to which file can be found in `.angular-cli.json`.\r
+\r
+export const environment = {\r
+  production: false\r
+};\r
diff --git a/otf-frontend/client/src/favicon.ico b/otf-frontend/client/src/favicon.ico
new file mode 100644 (file)
index 0000000..2cfcef1
Binary files /dev/null and b/otf-frontend/client/src/favicon.ico differ
diff --git a/otf-frontend/client/src/global-shims.ts b/otf-frontend/client/src/global-shims.ts
new file mode 100644 (file)
index 0000000..c6bec7f
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+(window as any).global = window;\r
+\r
+export const NOOP = 0;
\ No newline at end of file
diff --git a/otf-frontend/client/src/index.html b/otf-frontend/client/src/index.html
new file mode 100644 (file)
index 0000000..f6daf32
--- /dev/null
@@ -0,0 +1,36 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+#############################################################################-->\r
+\r
+\r
+<!doctype html>\r
+    <html lang="en">\r
+    <head>\r
+        <meta charset="utf-8">\r
+        <title>Open Test Framework</title>\r
+        <base href="/">\r
+\r
+        <meta name="viewport" content="width=device-width, initial-scale=1">\r
+        <link rel="icon" type="image/x-icon" href="favicon.ico">\r
+    </head>\r
+    <body>\r
+        <app-root>\r
+            <div class="spinner">\r
+                <div class="bounce1"></div>\r
+                <div class="bounce2"></div>\r
+                <div class="bounce3"></div>\r
+            </div>\r
+        </app-root>\r
+    </body>\r
+</html>\r
diff --git a/otf-frontend/client/src/main.ts b/otf-frontend/client/src/main.ts
new file mode 100644 (file)
index 0000000..fe30c76
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { enableProdMode } from '@angular/core';\r
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\r
+\r
+import { AppModule } from './app/app.module';\r
+import { environment } from './environments/environment';\r
+\r
+if (environment.production) {\r
+    enableProdMode();\r
+}\r
+\r
+platformBrowserDynamic()\r
+    .bootstrapModule(AppModule)\r
+    .catch(err => console.log(err));\r
diff --git a/otf-frontend/client/src/polyfills.ts b/otf-frontend/client/src/polyfills.ts
new file mode 100644 (file)
index 0000000..404f96f
--- /dev/null
@@ -0,0 +1,85 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+/**\r
+ * This file includes polyfills needed by Angular and is loaded before the app.\r
+ * You can add your own extra polyfills to this file.\r
+ *\r
+ * This file is divided into 2 sections:\r
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\r
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\r
+ *      file.\r
+ *\r
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that\r
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\r
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\r
+ *\r
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html\r
+ */\r
+\r
+/***************************************************************************************************\r
+ * BROWSER POLYFILLS\r
+ */\r
+\r
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/\r
+import 'core-js/es6/symbol';\r
+import 'core-js/es6/object';\r
+import 'core-js/es6/function';\r
+import 'core-js/es6/parse-int';\r
+import 'core-js/es6/parse-float';\r
+import 'core-js/es6/number';\r
+import 'core-js/es6/math';\r
+import 'core-js/es6/string';\r
+import 'core-js/es6/date';\r
+import 'core-js/es6/array';\r
+import 'core-js/es6/regexp';\r
+import 'core-js/es6/map';\r
+import 'core-js/es6/weak-map';\r
+import 'core-js/es6/set';\r
+\r
+(window as any).global = window;\r
+\r
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */\r
+//import 'classlist.js';  // Run `npm install --save classlist.js`.\r
+\r
+/** Evergreen browsers require these. **/\r
+import 'core-js/es6/reflect';\r
+import 'core-js/es7/reflect';\r
+\r
+/**\r
+ * Required to support Web Animations `@angular/platform-browser/animations`.\r
+ * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation\r
+ **/\r
+//import 'web-animations-js';  // Run `npm install --save web-animations-js`.\r
+\r
+/***************************************************************************************************\r
+ * Zone JS is required by Angular itself.\r
+ */\r
+import 'zone.js/dist/zone'; // Included with Angular CLI.\r
+\r
+/***************************************************************************************************\r
+ * APPLICATION IMPORTS\r
+ */\r
+\r
+/**\r
+ * Date, currency, decimal and percent pipes.\r
+ * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10\r
+ */\r
+// import 'intl';  // Run `npm install --save intl`.\r
+/**\r
+ * Need to import at least one locale-data with intl.\r
+ */\r
+// import 'intl/locale-data/jsonp/en';\r
diff --git a/otf-frontend/client/src/styles/_responsive.scss b/otf-frontend/client/src/styles/_responsive.scss
new file mode 100644 (file)
index 0000000..a5509a0
--- /dev/null
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+@media screen and (max-width: 992px) {\r
+    .push-right {\r
+        .sidebar {\r
+            left: 235px !important;\r
+        }\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/styles/_rtl.scss b/otf-frontend/client/src/styles/_rtl.scss
new file mode 100644 (file)
index 0000000..1249952
--- /dev/null
@@ -0,0 +1,90 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.rtl {\r
+    .sidebar {\r
+        left: auto !important;\r
+        right: 0 !important;\r
+        > ul.list-group {\r
+            padding: 0;\r
+        }\r
+    }\r
+    .main-container {\r
+        margin-left: 0 !important;\r
+        margin-right: 235px;\r
+    }\r
+    /*rtl dropdown items correction*/\r
+    .dropdown-menu {\r
+        text-align: right;\r
+    }\r
+    * {\r
+        direction: rtl;\r
+    }\r
+    .navbar * {\r
+        direction: ltr;\r
+    }\r
+\r
+    .sidebar * {\r
+        direction: ltr;\r
+    }\r
+\r
+    .navbar .dropdown-menu {\r
+        text-align: left;\r
+    }\r
+\r
+    .breadcrumb {\r
+        direction: ltr;\r
+        justify-content: flex-end;\r
+\r
+        * {\r
+            direction: ltr;\r
+        }\r
+    }\r
+\r
+    .datepicker-input {\r
+        direction: ltr;\r
+        .dropdown-menu {\r
+            direction: ltr;\r
+\r
+            * {\r
+                direction: ltr;\r
+            }\r
+        }\r
+    }\r
+\r
+    .input-group {\r
+        direction: ltr;\r
+    }\r
+}\r
+@media screen and (max-width: 992px) {\r
+    .rtl {\r
+        .navbar-brand {\r
+            direction: ltr;\r
+        }\r
+        .sidebar {\r
+            right: -235px !important;\r
+        }\r
+        .main-container {\r
+            margin-right: 0;\r
+        }\r
+        &.push-right {\r
+            .sidebar {\r
+                left: auto !important;\r
+                right: 0 !important;\r
+            }\r
+        }\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/styles/_spinner.scss b/otf-frontend/client/src/styles/_spinner.scss
new file mode 100644 (file)
index 0000000..8d24c4d
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.spinner {\r
+    position: absolute;\r
+    top: 50%;\r
+    left: 50%;\r
+    -ms-transform: translate(-50%,-50%); /* IE 9 */\r
+    -webkit-transform: translate(-50%,-50%); /* Safari */\r
+    transform: translate(-50%,-50%); /* Standard syntax */\r
+    width: 70px;\r
+    height: 70px;\r
+    > div {\r
+        width: 18px;\r
+        height: 18px;\r
+        background-color: #333;\r
+\r
+        border-radius: 100%;\r
+        display: inline-block;\r
+        -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;\r
+        animation: sk-bouncedelay 1.4s infinite ease-in-out both;\r
+    }\r
+    .bounce1 {\r
+        -webkit-animation-delay: -0.32s;\r
+        animation-delay: -0.32s;\r
+    }\r
+\r
+    .bounce2 {\r
+        -webkit-animation-delay: -0.16s;\r
+        animation-delay: -0.16s;\r
+    }\r
+}\r
+\r
+@-webkit-keyframes sk-bouncedelay {\r
+    0%, 80%, 100% { -webkit-transform: scale(0) }\r
+    40% { -webkit-transform: scale(1.0) }\r
+}\r
+\r
+@keyframes sk-bouncedelay {\r
+    0%, 80%, 100% {\r
+        -webkit-transform: scale(0);\r
+        transform: scale(0);\r
+    } 40% {\r
+        -webkit-transform: scale(1.0);\r
+        transform: scale(1.0);\r
+    }\r
+}\r
diff --git a/otf-frontend/client/src/styles/_utils.scss b/otf-frontend/client/src/styles/_utils.scss
new file mode 100644 (file)
index 0000000..61014d6
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+.fs-12 {\r
+    font-size: 12px;\r
+}\r
diff --git a/otf-frontend/client/src/styles/app.scss b/otf-frontend/client/src/styles/app.scss
new file mode 100644 (file)
index 0000000..3e6516b
--- /dev/null
@@ -0,0 +1,187 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+/* You can add global styles to this file, and also import other style files */\r
+$zindex-dropdown:                   900 !default;\r
+$zindex-sticky:                     920 !default;\r
+$zindex-fixed:                      950 !default;\r
+$font-size-base:                    .8rem !default;\r
+@import "../../../node_modules/bootstrap/scss/bootstrap";\r
+@import "spinner";\r
+@import "utils";\r
+@import "rtl";\r
+@import "responsive";\r
+\r
+.list-group-item {\r
+    padding: .5rem 1rem;\r
+}\r
+\r
+@media print {\r
+    .breadcrumb {\r
+        display: none !important;\r
+    }\r
+}\r
+\r
+@import '~@angular/material/theming';\r
+// Plus imports for other components in your app.\r
+\r
+// Include the common styles for Angular Material. We include this here so that you only\r
+// have to load a single css file for Angular Material in your app.\r
+// Be sure that you only ever include this mixin once!\r
+@include mat-core();\r
+\r
+// Define the palettes for your theme using the Material Design palettes available in palette.scss\r
+// (imported above). For each palette, you can optionally specify a default, lighter, and darker\r
+// hue. Available color palettes: https://material.io/design/color/\r
+$candy-app-primary: mat-palette($mat-blue, 900);\r
+\r
+$candy-app-accent:  mat-palette($mat-orange, A400);\r
+\r
+// The warn palette is optional (defaults to red).\r
+$candy-app-warn:    mat-palette($mat-deep-orange, A700);\r
+\r
+// Create the theme object (a Sass map containing all of the palettes).\r
+$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);\r
+\r
+// Include theme styles for core and each component used in your app.\r
+// Alternatively, you can import and @include the theme mixins for each component\r
+// that you are using.\r
+@include angular-material-theme($candy-app-theme);\r
+\r
+@import "../../../node_modules/perfect-scrollbar/css/perfect-scrollbar.css";\r
+//@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';\r
+@import url('https://fonts.googleapis.com/icon?family=Material+Icons');\r
+@import "../../../node_modules/codemirror/lib/codemirror.css";\r
+@import "../../../node_modules/codemirror/theme/eclipse.css";\r
+\r
+@import 'ag-grid-community/dist/styles/ag-grid.css';\r
+@import 'ag-grid-community/dist/styles/ag-theme-material.css';\r
+\r
+@font-face {\r
+    font-family: 'Material Icons';\r
+    font-style: normal;\r
+    font-weight: 400;\r
+    src: local('Material Icons'), \r
+         local('MaterialIcons-Regular'), \r
+         url(https://fonts.gstatic.com/s/materialicons/v21/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2');\r
+  }\r
+\r
+mat-icon{\r
+    font-family: 'Material Icons' !important\r
+}\r
+\r
+.embedded {\r
+    box-shadow: inset 0px 11px 8px -10px #CCC, inset 0px -11px 8px -10px #CCC\r
+}\r
+\r
+.mat-mini-fab .mat-button-wrapper>*{\r
+    vertical-align: 6px !important;\r
+}\r
+\r
+button:focus{\r
+    outline: none;\r
+}\r
+\r
+mat-card {\r
+    padding: 0px !important;\r
+}\r
+\r
+mat-card-header {\r
+    padding: 15px !important;\r
+    padding-top: 10px !important;\r
+    padding-bottom: 10px !important;\r
+}\r
+\r
+.mat-card-header-text {\r
+    width: 100% !important;\r
+}\r
+\r
+mat-card-title {\r
+    margin: 0px !important;\r
+    padding: 0px !important;\r
+}\r
+\r
+mat-card-title button {\r
+    padding: 3px 8px !important;\r
+    line-height: 5px !important;\r
+}\r
+\r
+mat-card-title button mat-icon {\r
+    font-size: 22px !important;\r
+}\r
+\r
+mat-card-content {\r
+    padding: 24px !important;\r
+    padding-top: 10px !important;\r
+}\r
+\r
+.highlight-task-running:not(.djs-connection) .djs-visual > :nth-child(1) {\r
+    fill: rgb(186, 186, 255) !important; /* color elements as green */\r
+    opacity: .7;\r
+}\r
+\r
+.highlight-task-completed:not(.djs-connection) .djs-visual > :nth-child(1) {\r
+    fill: rgb(92, 223, 92) !important; /* color elements as green */\r
+}\r
+\r
+.highlight-task-failed:not(.djs-connection) .djs-visual > :nth-child(1) {\r
+    fill: rgb(255, 83, 83) !important; /* color elements as green */\r
+}\r
+\r
+.dropdown-item {\r
+    cursor: pointer;\r
+}\r
+\r
+tr:nth-child(even){background-color: #f9f9f9;}\r
+\r
+html {\r
+    height: 100%\r
+}\r
+\r
+body {\r
+    height: calc(100% - 56px);\r
+}\r
+\r
+.main-container {\r
+    height: 100%;\r
+    overflow: scroll !important;\r
+}\r
+\r
+.form-buttons {\r
+    position:absolute;\r
+    bottom:0;\r
+    right:0; \r
+    z-index:1000;\r
+    margin-bottom:-18px\r
+}\r
+\r
+.upload-progress {\r
+    position: absolute;\r
+    background-color: green;\r
+    height: 100%;\r
+    opacity: .5;\r
+    top: 0; \r
+    left: 0;\r
+}\r
+app-form-generator textarea,\r
+app-form-generator input {\r
+    width: 100%;\r
+}\r
+\r
+.loader-modal-container .mat-dialog-container{\r
+    border-radius: 100px;\r
+    opacity: 0.1;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/test.ts b/otf-frontend/client/src/test.ts
new file mode 100644 (file)
index 0000000..34bfd28
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files\r
+\r
+import 'zone.js/dist/long-stack-trace-zone';\r
+import 'zone.js/dist/proxy.js';\r
+import 'zone.js/dist/sync-test';\r
+import 'zone.js/dist/jasmine-patch';\r
+import 'zone.js/dist/async-test';\r
+import 'zone.js/dist/fake-async-test';\r
+import { getTestBed } from '@angular/core/testing';\r
+import {\r
+  BrowserDynamicTestingModule,\r
+  platformBrowserDynamicTesting\r
+} from '@angular/platform-browser-dynamic/testing';\r
+\r
+// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.\r
+declare const __karma__: any;\r
+declare const require: any;\r
+\r
+// Prevent Karma from running prematurely.\r
+__karma__.loaded = function () {};\r
+\r
+// First, initialize the Angular testing environment.\r
+getTestBed().initTestEnvironment(\r
+  BrowserDynamicTestingModule,\r
+  platformBrowserDynamicTesting()\r
+);\r
+// Then we find all the tests.\r
+const context = require.context('./', true, /\.spec\.ts$/);\r
+// And load the modules.\r
+context.keys().map(context);\r
+// Finally, start Karma to run the tests.\r
+__karma__.start();\r
diff --git a/otf-frontend/client/src/tsconfig.app.json b/otf-frontend/client/src/tsconfig.app.json
new file mode 100644 (file)
index 0000000..bdd3b1c
--- /dev/null
@@ -0,0 +1,20 @@
+{\r
+  "extends": "../config/tsconfig.json",\r
+  "compilerOptions": {\r
+    "outDir": "../out-tsc/app",\r
+    "baseUrl": "./",\r
+    "module": "es2015",\r
+    "types": [],\r
+    "paths": {\r
+      "fs": [\r
+        "./global-shims"\r
+      ]\r
+    },\r
+    "experimentalDecorators": true,\r
+    "allowJs": true\r
+  },\r
+  "exclude": [\r
+    "test.ts",\r
+    "**/*.spec.ts"\r
+  ]\r
+}
\ No newline at end of file
diff --git a/otf-frontend/client/src/tsconfig.json b/otf-frontend/client/src/tsconfig.json
new file mode 100644 (file)
index 0000000..e023398
--- /dev/null
@@ -0,0 +1,21 @@
+{\r
+    "extends": "../config/tsconfig.json",\r
+    "compilerOptions": {\r
+      "outDir": "../out-tsc/app",\r
+      "baseUrl": "./",\r
+      "module": "es2015",\r
+      "types": [],\r
+      "paths": {\r
+        "fs": [\r
+          "./global-shims"\r
+        ]\r
+      },\r
+      "experimentalDecorators": true,\r
+      "allowJs": true\r
+    },\r
+    "exclude": [\r
+      "test.ts",\r
+      "**/*.spec.ts"\r
+    ]\r
+  }\r
+  
\ No newline at end of file
diff --git a/otf-frontend/client/src/tsconfig.spec.json b/otf-frontend/client/src/tsconfig.spec.json
new file mode 100644 (file)
index 0000000..2046c9e
--- /dev/null
@@ -0,0 +1,21 @@
+{\r
+  "extends": "../config/tsconfig.json",\r
+  "compilerOptions": {\r
+    "outDir": "../out-tsc/spec",\r
+    "baseUrl": "./",\r
+    "module": "commonjs",\r
+    "target": "es5",\r
+    "types": [\r
+      "jasmine",\r
+      "node"\r
+    ]\r
+  },\r
+  "files": [\r
+    "test.ts",\r
+    "polyfills.ts"\r
+  ],\r
+  "include": [\r
+    "**/*.spec.ts",\r
+    "**/*.d.ts"\r
+  ]\r
+}\r
diff --git a/otf-frontend/client/src/typings.d.ts b/otf-frontend/client/src/typings.d.ts
new file mode 100644 (file)
index 0000000..b179036
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+/* SystemJS module definition */\r
+declare var module: NodeModule;\r
+interface NodeModule {\r
+    id: string;\r
+}\r
+\r
+declare var fs: any;\r
+\r
+declare module "*.json" {\r
+    const value: any;\r
+    export default value;\r
+}
\ No newline at end of file
diff --git a/otf-frontend/helm/otf-frontend/Chart.yaml b/otf-frontend/helm/otf-frontend/Chart.yaml
new file mode 100644 (file)
index 0000000..2496e87
--- /dev/null
@@ -0,0 +1,5 @@
+apiVersion: v1\r
+appVersion: "1.0"\r
+description: A Helm chart the OTF Frontend\r
+name: otf-frontend\r
+version: 0.0.5-SNAPSHOT\r
diff --git a/otf-frontend/helm/otf-frontend/templates/deployment.yaml b/otf-frontend/helm/otf-frontend/templates/deployment.yaml
new file mode 100644 (file)
index 0000000..4d6c3e8
--- /dev/null
@@ -0,0 +1,218 @@
+apiVersion: extensions/v1beta1\r
+kind: Deployment\r
+metadata:\r
+  name: {{ .Values.appName}}\r
+  namespace: {{.Values.namespace}}\r
+  labels:\r
+    app: {{ .Values.appName}}\r
+    version: {{.Values.version}}\r
+spec:\r
+  revisionHistoryLimit: 1   # keep one replica set to allow rollback\r
+  minReadySeconds: 10\r
+  strategy:\r
+    # indicate which strategy we want for rolling update\r
+    type: RollingUpdate\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 1\r
+  {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+  replicas: {{ .Values.replicas.prod}}\r
+  {{ else if  eq .Values.env "st"}}\r
+  replicas: {{ .Values.replicas.st}}\r
+  {{ else }}\r
+  replicas: {{ .Values.replicas.dev}}\r
+  {{ end }}\r
+  selector:\r
+    matchLabels:\r
+      app: {{ .Values.appName}}\r
+      version: {{.Values.version}}\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: {{ .Values.appName}}\r
+        version: {{.Values.version}}\r
+    spec:\r
+      serviceAccount: default\r
+      volumes:\r
+      - name: {{ .Values.appName}}-cert-volume\r
+        secret:\r
+          secretName: {{.Values.sharedCert}}\r
+          optional: true\r
+          items:\r
+          - key: PEM_CERT\r
+            path: otf.pem\r
+          - key: PEM_KEY\r
+            path: privateKey.pem\r
+      containers:\r
+      - name: {{ .Values.appName}}\r
+        image: {{ .Values.image}}\r
+        imagePullPolicy: Always\r
+        ports:\r
+        - name: https\r
+          containerPort: 443\r
+          nodePort: {{.Values.nodePort}}\r
+          protocol: TCP\r
+        {{ if eq .Values.env "st"}}\r
+        resources:\r
+          limits:\r
+            memory: "5Gi"\r
+            cpu: "3"\r
+          requests:\r
+            memory: "2Gi"\r
+            cpu: "1"\r
+        {{else}}\r
+        resources:\r
+          limits:\r
+            memory: "10Gi"\r
+            cpu: "6"\r
+          requests:\r
+            memory: "4Gi"\r
+            cpu: "2"\r
+        {{end}}\r
+        env:\r
+        - name: ENV\r
+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: "production"\r
+          {{ else if eq .Values.env "st" }}\r
+          value: "system_test"\r
+          {{ else }}\r
+          value: "development"\r
+          {{ end }}\r
+        - name: NAMESPACE\r
+          value: {{.Values.namespace}}\r
+        - name: APP_NAME\r
+          value: {{ .Values.appName}}\r
+        - name: APP_VERSION\r
+          value: {{.Values.version}}\r
+        - name: OTF_URL\r
+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{.Values.otf.OTF_URL.prod | quote}}\r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{.Values.otf.OTF_URL.st | quote}}\r
+          {{ else }}\r
+          value: {{.Values.otf.OTF_URL.dev | quote}}\r
+          {{ end }}\r
+        - name: OTF_EMAIL\r
+          value: {{.Values.otf.OTF_EMAIL | quote}}\r
+        - name: AUTHENTICATION_SECRET\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: authentication_secret\r
+              optional: true\r
+        - name: SERVICEAPI_URL\r
+          {{ if eq .Values.env "prod" }}\r
+          value: {{.Values.serviceApi.prod.SERVICEAPI_URL | quote}}\r
+          {{ else if eq .Values.env "prod-dr" }}\r
+          value: {{.Values.serviceApi.prod_dr.SERVICEAPI_URL | quote}}\r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{.Values.serviceApi.st.SERVICEAPI_URL | quote}}\r
+          {{ else }}\r
+          value: {{.Values.serviceApi.dev.SERVICEAPI_URL | quote}}\r
+          {{ end }}\r
+        - name: SERVICEAPI_URIEXECUTETESTINSTANCE\r
+          {{ if eq .Values.env "prod" }}\r
+          value: {{.Values.serviceApi.prod.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}\r
+          {{ else if eq .Values.env "prod-dr" }}\r
+          value: {{.Values.serviceApi.prod_dr.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}          \r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{.Values.serviceApi.st.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}\r
+          {{ else }}\r
+          value: {{.Values.serviceApi.dev.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}\r
+          {{ end }}\r
+        - name: SERVICEAPI_AAFID\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_id\r
+              optional: true\r
+        - name: SERVICEAPI_AAFPASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_mech_password\r
+              optional: true\r
+        - name: CAMUNDAAPI_URL\r
+          {{ if eq .Values.env "prod" }}\r
+          value: {{ .Values.camundaApi.prod.CAMUNDAAPI_URL | quote}}\r
+          {{ else if eq .Values.env "prod-dr" }}\r
+          value: {{ .Values.camundaApi.prod_dr.CAMUNDAAPI_URL | quote}}          \r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{ .Values.camundaApi.st.CAMUNDAAPI_URL | quote}}\r
+          {{ else }}\r
+          value: {{ .Values.camundaApi.dev.CAMUNDAAPI_URL | quote}}\r
+          {{ end }}\r
+        - name: CAMUNDAAPI_AAFID\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_id\r
+              optional: true\r
+        - name: CAMUNDAAPI_AAFPASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_mech_password\r
+              optional: true\r
+        - name: MONGO_BASEURL\r
+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.mongo.prod.MONGO_BASEURL | quote}}\r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{ .Values.mongo.st.MONGO_BASEURL | quote}}\r
+          {{ else }}\r
+          value: {{ .Values.mongo.dev.MONGO_BASEURL | quote}}\r
+          {{ end }}\r
+        - name: MONGO_DBOTF\r
+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.mongo.prod.MONGO_DBOTF | quote }}\r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{ .Values.mongo.st.MONGO_DBOTF | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.mongo.dev.MONGO_DBOTF | quote }}\r
+          {{ end }}\r
+        - name: MONGO_REPLICASET\r
+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.mongo.prod.MONGO_REPLICASET | quote }}\r
+          {{ else if eq .Values.env "st" }}\r
+          value: {{ .Values.mongo.st.MONGO_REPLICASET | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.mongo.dev.MONGO_REPLICASET | quote }}\r
+          {{ end }}\r
+        - name: MONGO_USERNAME\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: mongo_username\r
+              optional: true\r
+        - name: MONGO_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: mongo_password\r
+              optional: true\r
+        volumeMounts:\r
+        - name: {{.Values.appName}}-cert-volume\r
+          mountPath: /home/node/server/config/cert/\r
+        livenessProbe:\r
+          httpGet:\r
+            path: {{ .Values.healthEndpoint }}\r
+            port: https\r
+            scheme: HTTPS\r
+            httpHeaders:\r
+              - name: X-Custom-Header\r
+                value: Alive\r
+          initialDelaySeconds: 30\r
+          timeoutSeconds: 30\r
+          periodSeconds: 60\r
+        readinessProbe:\r
+          httpGet:\r
+            path: {{ .Values.healthEndpoint }}\r
+            port: https\r
+            scheme: HTTPS\r
+            httpHeaders:\r
+              - name: X-Custom-Header\r
+                value: Ready\r
+          initialDelaySeconds: 30\r
+          timeoutSeconds: 30\r
+          periodSeconds: 30\r
+      restartPolicy: Always\r
diff --git a/otf-frontend/helm/otf-frontend/templates/secret.yaml b/otf-frontend/helm/otf-frontend/templates/secret.yaml
new file mode 100644 (file)
index 0000000..175427c
--- /dev/null
@@ -0,0 +1,9 @@
+apiVersion: v1\r
+kind: Secret\r
+metadata:\r
+  name: {{ .Values.appName}}\r
+type: Opaque\r
+data:\r
+  mongo_username: {{ .Values.mongo.username | b64enc}}\r
+  mongo_password: {{ .Values.mongo.password | b64enc}}\r
+  authentication_secret: {{.Values.AUTHENTICATION_SECRET | b64enc}}\r
diff --git a/otf-frontend/helm/otf-frontend/templates/service.yaml b/otf-frontend/helm/otf-frontend/templates/service.yaml
new file mode 100644 (file)
index 0000000..0b0badd
--- /dev/null
@@ -0,0 +1,18 @@
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: {{ .Values.appName }}\r
+  namespace: {{ .Values.namespace }}\r
+  labels:\r
+    app: {{ .Values.appName }}\r
+    version: {{ .Values.version }}\r
+spec:\r
+  type: NodePort\r
+  ports:\r
+  - name: https\r
+    protocol: TCP\r
+    port: 443\r
+    nodePort: {{ .Values.nodePort }}\r
+  selector:\r
+    app: {{ .Values.appName }}\r
+    version: {{ .Values.version }}\r
diff --git a/otf-frontend/helm/otf-frontend/values.yaml b/otf-frontend/helm/otf-frontend/values.yaml
new file mode 100644 (file)
index 0000000..571d323
--- /dev/null
@@ -0,0 +1,59 @@
+appName: otf-frontend\r
+version: 0.0.4-SNAPSHOT\r
+image: otf-frontend\r
+namespace: \r
+nodePort: 32524\r
+replicas:\r
+  dev: 2\r
+  st: 1\r
+  prod: 2\r
+env: dev\r
+AUTHENTICATION_SECRET: ""\r
+serviceApi:\r
+  prod:\r
+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"\r
+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/"\r
+  prod_dr:\r
+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"\r
+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/" \r
+  st:\r
+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"\r
+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/"\r
+  dev:\r
+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"\r
+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/"\r
+camundaApi:\r
+  prod:\r
+    CAMUNDAAPI_URL: "https://localhost:31313/"\r
+  prod_dr:\r
+    CAMUNDAAPI_URL: "https://localhost:31313/"\r
+  st:\r
+    CAMUNDAAPI_URL: "https://localhost:31313/"\r
+  dev:\r
+    CAMUNDAAPI_URL: "https://localhost:31313/"\r
+mongo:\r
+  prod:\r
+    MONGO_BASEURL: "localhost:18720,localhost:18720,localhost:18720/"\r
+    MONGO_DBOTF: "otf"\r
+    MONGO_REPLICASET: "otf-rs-prod2"\r
+  st:\r
+    MONGO_BASEURL: "localhost:27017,localhost:27017,localhost:27017/"\r
+    MONGO_DBOTF: "otf_st"\r
+    MONGO_REPLICASET: "mongoOTF"\r
+  dev:\r
+    MONGO_BASEURL: "localhost:27017,localhost:27017,localhost:27017/"\r
+    MONGO_DBOTF: "otf"\r
+    MONGO_REPLICASET: "mongoOTF"\r
+  username: ""\r
+  password: ""\r
+otf:\r
+  OTF_EMAIL: "OTF_NO-REPLY@localhost.com"\r
+  OTF_URL:\r
+    dev: "https://localhost:32524/"\r
+    st: "https://localhost:32524/"\r
+    prod: "https://localhost:32524/"\r
+\r
+sharedSecret: otf-aaf-credential-generator\r
+sharedCert: otf-cert-secret-builder\r
+\r
+healthEndpoint: otf/api/health/v1\r
diff --git a/otf-frontend/package.json b/otf-frontend/package.json
new file mode 100644 (file)
index 0000000..84fa16d
--- /dev/null
@@ -0,0 +1,168 @@
+{\r
+  "name": "otf-frontend",\r
+  "description": "This module is made up of the OTF User Interface, and a Node.js server to serve it.",\r
+  "keywords": [\r
+    "otf",\r
+    "open testing framework",\r
+    "best ui"\r
+  ],\r
+  "bugs": "lol",\r
+  "licenses": [\r
+    "TBD"\r
+  ],\r
+  "authors": "Raj Patel",\r
+  "contributors": [\r
+    "Justin Meiliinger, Adam Ordway, Raj Patel, Rohan Patel"\r
+  ],\r
+  "version": "1.0.0a",\r
+  "scripts": {\r
+    "ng": "ng",\r
+    "start": "node server/src/app.js",\r
+    "debug": "node server/src/app.js",\r
+    "build": "ng build --prod --output-path ./client/dist/ --build-optimizer=false",\r
+    "gitbuild": "ng build --prod --base-href /start-angular/SB-Admin-BS4-Angular-6/master/dist/",\r
+    "test": "ng test",\r
+    "test-ci": "TEST_CI=true ng test",\r
+    "lint": "ng lint",\r
+    "e2e": "ng e2e",\r
+    "postinstall": "ng add ng-cli-pug-loader@0.1.7 && node ./ng-add-pug-loader.js"\r
+  },\r
+  "directories": {\r
+    "lib": "server/src/feathers"\r
+  },\r
+  "private": true,\r
+  "dependencies": {\r
+    "@amcharts/amcharts4": "^4.5.3",\r
+    "@angular/animations": "^6.1.7",\r
+    "@angular/cdk": "^6.4.7",\r
+    "@angular/common": "^6.1.7",\r
+    "@angular/compiler": "^6.1.7",\r
+    "@angular/core": "^6.1.7",\r
+    "@angular/forms": "^6.1.7",\r
+    "@angular/http": "^6.1.7",\r
+    "@angular/material": "^6.4.7",\r
+    "@angular/platform-browser": "^7.0.2",\r
+    "@angular/platform-browser-dynamic": "^7.0.2",\r
+    "@angular/router": "^6.1.7",\r
+    "@casl/ability": "^3.1.2",\r
+    "@casl/angular": "^2.1.0",\r
+    "@casl/mongoose": "^2.3.1",\r
+    "@feathersjs/authentication": "^2.1.13",\r
+    "@feathersjs/authentication-client": "^1.0.11",\r
+    "@feathersjs/authentication-jwt": "^2.0.7",\r
+    "@feathersjs/authentication-local": "^1.2.7",\r
+    "@feathersjs/client": "^3.7.8",\r
+    "@feathersjs/configuration": "^1.0.2",\r
+    "@feathersjs/errors": "^3.3.0",\r
+    "@feathersjs/express": "^1.2.3",\r
+    "@feathersjs/feathers": "^3.1.7",\r
+    "@feathersjs/socketio": "^3.2.7",\r
+    "@feathersjs/socketio-client": "^1.2.1",\r
+    "@ng-bootstrap/ng-bootstrap": "^2.0.0",\r
+    "@ngx-translate/core": "^10.0.1",\r
+    "@ngx-translate/http-loader": "^3.0.1",\r
+    "@types/socket.io-client": "^1.4.32",\r
+    "ag-grid-angular": "^20.2.0",\r
+    "ag-grid-community": "^20.2.0",\r
+    "agenda": "^2.0.2",\r
+    "angular-datatables": "^6.0.0",\r
+    "angular-particle": "^1.0.4",\r
+    "angular-resizable-element": "^3.2.4",\r
+    "axios": "^0.19.0",\r
+    "bootstrap": "^4.3.1",\r
+    "bpmn-font": "^0.8.0",\r
+    "bpmn-js": "^2.5.2",\r
+    "bpmn-js-properties-panel": "^0.32.1",\r
+    "btoa": "^1.2.1",\r
+    "camunda-bpmn-moddle": "^3.2.0",\r
+    "classlist.js": "^1.1.20150312",\r
+    "clean": "^4.0.2",\r
+    "codemirror": "^5.41.0",\r
+    "cors": "^2.8.5",\r
+    "datatables.net": "^1.10.19",\r
+    "datatables.net-dt": "^1.10.19",\r
+    "diagram-js-minimap": "^1.3.0",\r
+    "dot-object": "^1.9.0",\r
+    "express-rate-limit": "^3.3.2",\r
+    "feathers-authentication-management": "^2.0.1",\r
+    "feathers-hooks-common": "^4.17.14",\r
+    "feathers-mongoose": "^6.2.0",\r
+    "feathers-permissions": "^0.2.1",\r
+    "file-saver": "^2.0.1",\r
+    "font-awesome": "^4.7.0",\r
+    "helmet": "^3.14.0",\r
+    "http-response-object": "^3.0.1",\r
+    "jquery": "^3.4.1",\r
+    "json-beautify": "^1.0.1",\r
+    "jsonbeautify": "0.0.1",\r
+    "lodash.pick": "^4.4.0",\r
+    "mat-progress-buttons": "^7.0.10",\r
+    "material-design-icons": "^3.0.1",\r
+    "moment": "^2.22.2",\r
+    "mongoose": "^5.6.4",\r
+    "mongoose-gridfs": "^0.5.0",\r
+    "multer": "^1.4.1",\r
+    "ng-cli-pug-loader": "^0.1.7",\r
+    "ng2-codemirror": "^1.1.3",\r
+    "ng2-completer": "^2.0.8",\r
+    "ng2-file-upload": "^1.3.0",\r
+    "ngx-cookie-service": "^2.0.0",\r
+    "ngx-filter-pipe": "^2.1.2",\r
+    "ngx-json-viewer": "^2.4.0",\r
+    "ngx-material-timepicker": "^2.8.4",\r
+    "ngx-perfect-scrollbar": "^7.0.0",\r
+    "ngx-socket-io": "^2.1.1",\r
+    "npm": "^6.10.1",\r
+    "object.pick": "^1.3.0",\r
+    "pickle-rick": "^0.1.0",\r
+    "rate-limit-mongo": "^1.0.3",\r
+    "redis": "^2.8.0",\r
+    "rxjs-compat": "^6.4.0",\r
+    "sendmail": "^1.4.1",\r
+    "serve-favicon": "^2.5.0",\r
+    "socket.io-client": "^2.2.0",\r
+    "unzip-stream": "^0.3.0",\r
+    "update": "^0.7.4",\r
+    "uuid": "^3.3.2",\r
+    "web-animations-js": "^2.3.1",\r
+    "winston": "^3.0.0",\r
+    "xml2js": "^0.4.19",\r
+    "yamljs": "^0.3.0",\r
+    "zone.js": "^0.8.26"\r
+  },\r
+  "devDependencies": {\r
+    "@angular-devkit/build-angular": "^0.6.8",\r
+    "@angular/cli": "^6.2.7",\r
+    "@angular/compiler-cli": "^6.1.7",\r
+    "@angular/language-service": "^6.1.7",\r
+    "@types/datatables.net": "^1.10.16",\r
+    "@types/jasmine": "^2.8.11",\r
+    "@types/jasminewd2": "^2.0.6",\r
+    "@types/jquery": "^3.3.29",\r
+    "@types/node": "^9.6.52",\r
+    "apply-loader": "^2.0.0",\r
+    "codelyzer": "~4.2.1",\r
+    "eslint": "^5.8.0",\r
+    "eslint-plugin-import": "^2.14.0",\r
+    "eslint-plugin-node": "^7.0.1",\r
+    "eslint-plugin-promise": "^4.0.1",\r
+    "eslint-plugin-standard": "^4.0.0",\r
+    "jasmine-core": "^3.3.0",\r
+    "jasmine-spec-reporter": "~4.2.1",\r
+    "karma": "~2.0.0",\r
+    "karma-chrome-launcher": "~2.2.0",\r
+    "karma-cli": "~1.0.1",\r
+    "karma-coverage-istanbul-reporter": "^1.4.2",\r
+    "karma-jasmine": "~1.1.1",\r
+    "karma-jasmine-html-reporter": "^1.4.0",\r
+    "mocha": "^5.2.0",\r
+    "protractor": "^5.4.2",\r
+    "pug": "^2.0.4",\r
+    "pug-loader": "^2.4.0",\r
+    "request": "^2.88.0",\r
+    "request-promise": "^4.2.2",\r
+    "ts-node": "~5.0.1",\r
+    "tslint": "~5.9.1",\r
+    "typescript": "~2.8.0"\r
+  }\r
+}\r
diff --git a/otf-frontend/server/config/.eslintrc b/otf-frontend/server/config/.eslintrc
new file mode 100644 (file)
index 0000000..2377cf4
--- /dev/null
@@ -0,0 +1,474 @@
+{\r
+  "parserOptions": {\r
+    "ecmaVersion": 2018,\r
+    "ecmaFeatures": {\r
+      "jsx": true\r
+    },\r
+    "sourceType": "module"\r
+  },\r
+  "env": {\r
+    "es6": true,\r
+    "node": true\r
+  },\r
+  "plugins": [\r
+    "import",\r
+    "node",\r
+    "promise",\r
+    "standard"\r
+  ],\r
+  "globals": {\r
+    "document": false,\r
+    "navigator": false,\r
+    "window": false\r
+  },\r
+  "rules": {\r
+    "accessor-pairs": "error",\r
+    "arrow-spacing": [\r
+      "error",\r
+      {\r
+        "before": true,\r
+        "after": true\r
+      }\r
+    ],\r
+    "block-spacing": [\r
+      "error",\r
+      "always"\r
+    ],\r
+    "brace-style": [\r
+      "error",\r
+      "1tbs",\r
+      {\r
+        "allowSingleLine": true\r
+      }\r
+    ],\r
+    "camelcase": [\r
+      "error",\r
+      {\r
+        "properties": "never"\r
+      }\r
+    ],\r
+    "comma-dangle": [\r
+      "error",\r
+      {\r
+        "arrays": "never",\r
+        "objects": "never",\r
+        "imports": "never",\r
+        "exports": "never",\r
+        "functions": "never"\r
+      }\r
+    ],\r
+    "comma-spacing": [\r
+      "error",\r
+      {\r
+        "before": false,\r
+        "after": true\r
+      }\r
+    ],\r
+    "comma-style": [\r
+      "error",\r
+      "last"\r
+    ],\r
+    "constructor-super": "error",\r
+    "curly": [\r
+      "error",\r
+      "multi-line"\r
+    ],\r
+    "dot-location": [\r
+      "error",\r
+      "property"\r
+    ],\r
+    "eol-last": "error",\r
+    "eqeqeq": [\r
+      "error",\r
+      "always",\r
+      {\r
+        "null": "ignore"\r
+      }\r
+    ],\r
+    "func-call-spacing": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "generator-star-spacing": [\r
+      "error",\r
+      {\r
+        "before": true,\r
+        "after": true\r
+      }\r
+    ],\r
+    "handle-callback-err": [\r
+      "error",\r
+      "^(err|error)$"\r
+    ],\r
+    "indent": [\r
+      "error",\r
+      "tab",\r
+      {\r
+        "SwitchCase": 1,\r
+        "VariableDeclarator": 1,\r
+        "outerIIFEBody": 1,\r
+        "MemberExpression": 1,\r
+        "FunctionDeclaration": {\r
+          "parameters": 1,\r
+          "body": 1\r
+        },\r
+        "FunctionExpression": {\r
+          "parameters": 1,\r
+          "body": 1\r
+        },\r
+        "CallExpression": {\r
+          "arguments": 1\r
+        },\r
+        "ArrayExpression": 1,\r
+        "ObjectExpression": 1,\r
+        "ImportDeclaration": 1,\r
+        "flatTernaryExpressions": false,\r
+        "ignoreComments": false\r
+      }\r
+    ],\r
+    "key-spacing": [\r
+      "error",\r
+      {\r
+        "beforeColon": false,\r
+        "afterColon": true\r
+      }\r
+    ],\r
+    "keyword-spacing": [\r
+      "error",\r
+      {\r
+        "before": true,\r
+        "after": true\r
+      }\r
+    ],\r
+    "new-cap": [\r
+      "error",\r
+      {\r
+        "newIsCap": true,\r
+        "capIsNew": false\r
+      }\r
+    ],\r
+    "new-parens": "error",\r
+    "no-array-constructor": "error",\r
+    "no-caller": "error",\r
+    "no-class-assign": "error",\r
+    "no-compare-neg-zero": "error",\r
+    "no-cond-assign": "error",\r
+    "no-const-assign": "error",\r
+    "no-constant-condition": [\r
+      "error",\r
+      {\r
+        "checkLoops": false\r
+      }\r
+    ],\r
+    "no-control-regex": "error",\r
+    "no-debugger": "error",\r
+    "no-delete-var": "error",\r
+    "no-dupe-args": "error",\r
+    "no-dupe-class-members": "error",\r
+    "no-dupe-keys": "error",\r
+    "no-duplicate-case": "error",\r
+    "no-empty-character-class": "error",\r
+    "no-empty-pattern": "error",\r
+    "no-eval": "error",\r
+    "no-ex-assign": "error",\r
+    "no-extend-native": "error",\r
+    "no-extra-bind": "error",\r
+    "no-extra-boolean-cast": "error",\r
+    "no-extra-parens": [\r
+      "error",\r
+      "functions"\r
+    ],\r
+    "no-fallthrough": "error",\r
+    "no-floating-decimal": "error",\r
+    "no-func-assign": "error",\r
+    "no-global-assign": "error",\r
+    "no-implied-eval": "error",\r
+    "no-inner-declarations": [\r
+      "error",\r
+      "functions"\r
+    ],\r
+    "no-invalid-regexp": "error",\r
+    "no-irregular-whitespace": "error",\r
+    "no-iterator": "error",\r
+    "no-label-var": "error",\r
+    "no-labels": [\r
+      "error",\r
+      {\r
+        "allowLoop": false,\r
+        "allowSwitch": false\r
+      }\r
+    ],\r
+    "no-lone-blocks": "error",\r
+    "no-mixed-operators": [\r
+      "error",\r
+      {\r
+        "groups": [\r
+          [\r
+            "==",\r
+            "!=",\r
+            "===",\r
+            "!==",\r
+            ">",\r
+            ">=",\r
+            "<",\r
+            "<="\r
+          ],\r
+          [\r
+            "&&",\r
+            "||"\r
+          ],\r
+          [\r
+            "in",\r
+            "instanceof"\r
+          ]\r
+        ],\r
+        "allowSamePrecedence": true\r
+      }\r
+    ],\r
+    "no-mixed-spaces-and-tabs": "error",\r
+    "no-multi-spaces": "error",\r
+    "no-multi-str": "error",\r
+    "no-multiple-empty-lines": [\r
+      "error",\r
+      {\r
+        "max": 1,\r
+        "maxEOF": 0\r
+      }\r
+    ],\r
+    "no-negated-in-lhs": "error",\r
+    "no-new": "error",\r
+    "no-new-func": "error",\r
+    "no-new-object": "error",\r
+    "no-new-require": "error",\r
+    "no-new-symbol": "error",\r
+    "no-new-wrappers": "error",\r
+    "no-obj-calls": "error",\r
+    "no-octal": "error",\r
+    "no-octal-escape": "error",\r
+    "no-path-concat": "error",\r
+    "no-proto": "error",\r
+    "no-redeclare": "error",\r
+    "no-regex-spaces": "error",\r
+    "no-return-assign": [\r
+      "error",\r
+      "except-parens"\r
+    ],\r
+    "no-return-await": "error",\r
+    "no-self-assign": "error",\r
+    "no-self-compare": "error",\r
+    "no-sequences": "error",\r
+    "no-shadow-restricted-names": "error",\r
+    "no-sparse-arrays": "error",\r
+    "no-template-curly-in-string": "error",\r
+    "no-this-before-super": "error",\r
+    "no-trailing-spaces": "error",\r
+    "no-undef": "error",\r
+    "no-undef-init": "error",\r
+    "no-unexpected-multiline": "error",\r
+    "no-unmodified-loop-condition": "error",\r
+    "no-unneeded-ternary": [\r
+      "error",\r
+      {\r
+        "defaultAssignment": false\r
+      }\r
+    ],\r
+    "no-unreachable": "error",\r
+    "no-unsafe-finally": "error",\r
+    "no-unsafe-negation": "error",\r
+    "no-unused-expressions": [\r
+      "error",\r
+      {\r
+        "allowShortCircuit": true,\r
+        "allowTernary": true,\r
+        "allowTaggedTemplates": true\r
+      }\r
+    ],\r
+    "no-unused-vars": [\r
+      "error",\r
+      {\r
+        "vars": "all",\r
+        "args": "none",\r
+        "ignoreRestSiblings": true\r
+      }\r
+    ],\r
+    "no-use-before-define": [\r
+      "error",\r
+      {\r
+        "functions": false,\r
+        "classes": false,\r
+        "variables": false\r
+      }\r
+    ],\r
+    "no-useless-call": "error",\r
+    "no-useless-computed-key": "error",\r
+    "no-useless-constructor": "error",\r
+    "no-useless-escape": "error",\r
+    "no-useless-rename": "error",\r
+    "no-useless-return": "error",\r
+    "no-whitespace-before-property": "error",\r
+    "no-with": "error",\r
+    "object-curly-spacing": [\r
+      "error",\r
+      "always"\r
+    ],\r
+    "object-property-newline": [\r
+      "error",\r
+      {\r
+        "allowMultiplePropertiesPerLine": true\r
+      }\r
+    ],\r
+    "one-var": [\r
+      "error",\r
+      {\r
+        "initialized": "never"\r
+      }\r
+    ],\r
+    "operator-linebreak": [\r
+      "error",\r
+      "after",\r
+      {\r
+        "overrides": {\r
+          "?": "before",\r
+          ":": "before"\r
+        }\r
+      }\r
+    ],\r
+    "padded-blocks": [\r
+      "error",\r
+      {\r
+        "blocks": "never",\r
+        "switches": "never",\r
+        "classes": "never"\r
+      }\r
+    ],\r
+    "prefer-promise-reject-errors": "error",\r
+    "quotes": [\r
+      "error",\r
+      "single",\r
+      {\r
+        "avoidEscape": true,\r
+        "allowTemplateLiterals": true\r
+      }\r
+    ],\r
+    "rest-spread-spacing": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "semi": [\r
+      "error",\r
+      "always"\r
+    ],\r
+    "semi-spacing": [\r
+      "error",\r
+      {\r
+        "before": false,\r
+        "after": true\r
+      }\r
+    ],\r
+    "space-before-blocks": [\r
+      "error",\r
+      "always"\r
+    ],\r
+    "space-before-function-paren": [\r
+      "error",\r
+      "always"\r
+    ],\r
+    "space-in-parens": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "space-infix-ops": "error",\r
+    "space-unary-ops": [\r
+      "error",\r
+      {\r
+        "words": true,\r
+        "nonwords": false\r
+      }\r
+    ],\r
+    "spaced-comment": [\r
+      "error",\r
+      "always",\r
+      {\r
+        "line": {\r
+          "markers": [\r
+            "*package",\r
+            "!",\r
+            "/",\r
+            ",",\r
+            "="\r
+          ]\r
+        },\r
+        "block": {\r
+          "balanced": true,\r
+          "markers": [\r
+            "*package",\r
+            "!",\r
+            ",",\r
+            ":",\r
+            "::",\r
+            "flow-include"\r
+          ],\r
+          "exceptions": [\r
+            "*"\r
+          ]\r
+        }\r
+      }\r
+    ],\r
+    "symbol-description": "error",\r
+    "template-curly-spacing": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "template-tag-spacing": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "unicode-bom": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "use-isnan": "error",\r
+    "valid-typeof": [\r
+      "error",\r
+      {\r
+        "requireStringLiterals": true\r
+      }\r
+    ],\r
+    "wrap-iife": [\r
+      "error",\r
+      "any",\r
+      {\r
+        "functionPrototypeMethods": true\r
+      }\r
+    ],\r
+    "yield-star-spacing": [\r
+      "error",\r
+      "both"\r
+    ],\r
+    "yoda": [\r
+      "error",\r
+      "never"\r
+    ],\r
+    "import/export": "error",\r
+    "import/first": "error",\r
+    "import/no-duplicates": "error",\r
+    "import/no-named-default": "error",\r
+    "import/no-webpack-loader-syntax": "error",\r
+    "node/no-deprecated-api": "error",\r
+    "node/process-exit-as-throw": "error",\r
+    "promise/param-names": "error",\r
+    "standard/array-bracket-even-spacing": [\r
+      "error",\r
+      "either"\r
+    ],\r
+    "standard/computed-property-even-spacing": [\r
+      "error",\r
+      "even"\r
+    ],\r
+    "standard/no-callback-literal": "error",\r
+    "standard/object-curly-even-spacing": [\r
+      "error",\r
+      "either"\r
+    ]\r
+  }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/server/config/cert/info.txt b/otf-frontend/server/config/cert/info.txt
new file mode 100644 (file)
index 0000000..0799b0b
--- /dev/null
@@ -0,0 +1 @@
+add certs here. requires two pem files (Cert and key)\r
diff --git a/otf-frontend/server/config/custom-environment-variables.json b/otf-frontend/server/config/custom-environment-variables.json
new file mode 100644 (file)
index 0000000..a261f9a
--- /dev/null
@@ -0,0 +1,29 @@
+{\r
+    "authentication": {\r
+        "secret": "AUTHENTICATION_SECRET"\r
+    },\r
+    "serviceApi": {\r
+        "url": "SERVICEAPI_URL",\r
+        "uriExecuteTestInstance": "SERVICEAPI_URIEXECUTETESTINSTANCE",\r
+        "aafId": "SERVICEAPI_AAFID",\r
+        "aafPassword": "SERVICEAPI_AAFPASSWORD"\r
+    },\r
+    "camundaApi": {\r
+        "url": "CAMUNDAAPI_URL",\r
+        "aafId": "CAMUNDAAPI_AAFID",\r
+        "aafPassword": "CAMUNDAAPI_AAFPASSWORD"\r
+    },\r
+    "mongo": {\r
+        "baseUrl": "MONGO_BASEURL",\r
+        "dbOtf": "MONGO_DBOTF",\r
+        "replicaSet": "MONGO_REPLICASET",\r
+        "username": "MONGO_USERNAME",\r
+        "password": "MONGO_PASSWORD"\r
+    },\r
+    "otf": {\r
+      "url" : "OTF_URL",\r
+      "email" : "OTF_EMAIL"\r
+    },\r
+    "env": "ENV"\r
+    \r
+}\r
diff --git a/otf-frontend/server/config/production.json b/otf-frontend/server/config/production.json
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/otf-frontend/server/src/agenda/agenda.js b/otf-frontend/server/src/agenda/agenda.js
new file mode 100644 (file)
index 0000000..5094b6b
--- /dev/null
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const logger = require('../lib/logger');\r
+const Agenda = require('agenda');\r
+const mongoData = require('config').mongo;\r
+const jobTypes = ['test-execution-job'];\r
+const agenda = new Agenda({\r
+       db: {\r
+               address: 'mongodb://' + mongoData.username + ':' + mongoData.password + '@' + mongoData.baseUrl + mongoData.dbOtf + '?replicaSet=' + mongoData.replicaSet,\r
+               collection: 'agenda'\r
+       }\r
+});\r
+\r
+module.exports = {\r
+       agenda: agenda,\r
+       initializeAgenda: function () {\r
+               // Load all job types\r
+               jobTypes.forEach(type => {\r
+                       require('./jobs/' + type)(agenda);\r
+               });\r
+\r
+               // Wait for the db connection to be established before starting agenda (sync).\r
+               agenda.on('ready', function () {\r
+                       logger.debug('Agenda successfully established a connection to MongoDB.');\r
+                       agenda.start();\r
+                       // agenda.processEvery('0.001 seconds');\r
+               });\r
+\r
+               async function graceful () {\r
+                       await agenda.stop();\r
+                       process.exit(0);\r
+               }\r
+\r
+               process.on('SIGTERM', graceful);\r
+               process.on('SIGINT', graceful);\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/agenda/controllers/test-execution-controller.js b/otf-frontend/server/src/agenda/controllers/test-execution-controller.js
new file mode 100644 (file)
index 0000000..56f07a2
--- /dev/null
@@ -0,0 +1,220 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const logger = require('../../lib/logger');\r
+const agenda = require('../agenda').agenda;\r
+const emitter = require('../result-emitter').emitter;\r
+const utils = require('../../lib/otf-util');\r
+const nodeUtil = require('util');\r
+\r
+const ObjectId = require('mongoose').Types.ObjectId;\r
+\r
+const TestSchedule = require('../models/test-schedule');\r
+\r
+const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\r
+\r
+module.exports = function (app) {\r
+       let scheduleTestResponse = { status: '', message: '' };\r
+       let cancelTestResponse = { status: '', message: '' };\r
+\r
+       // Main endpoint for scheduling\r
+       app.use('/' + app.get('base-path') + 'schedule-test', (req, res, next) => {\r
+               const authorizationHeader = req.headers.authorization;\r
+\r
+               const testInstanceId = req.body.testInstanceId;\r
+               const testInstanceStartDate = req.body.testInstanceStartDate;\r
+               const testInstanceExecFreqInSeconds = req.body.testInstanceExecFreqInSeconds;\r
+               const testInstanceEndDate = req.body.testInstanceEndDate;\r
+               const async = req.body.async;\r
+               const asyncTopic = req.body.asyncTopic;\r
+               const asyncMode = req.body.asyncMode;\r
+               const executorId = req.body.executorId;\r
+\r
+               let testSchedule = null;\r
+\r
+               try {\r
+                       testSchedule = new TestSchedule(testInstanceId, testInstanceStartDate, testInstanceExecFreqInSeconds,\r
+                               testInstanceEndDate, async, asyncTopic, asyncMode, executorId);\r
+               } catch (error) {\r
+                       scheduleTestResponse.status = 400;\r
+                       scheduleTestResponse.message = error.toString();\r
+                       next();\r
+\r
+                       return;\r
+               }\r
+\r
+               // The presence of this parameter indicates that we will be executing either job definition 2/3.\r
+               if (testSchedule.testInstanceStartDate) {\r
+                       if (testSchedule.testInstanceExecFreqInSeconds) {\r
+                               const job = agenda.create(\r
+                                       'executeTestAsync',\r
+                                       { testSchedule, authorizationHeader });\r
+                               job.schedule(testSchedule.testInstanceStartDate).repeatEvery(testSchedule.testInstanceExecFreqInSeconds + ' seconds', {\r
+                                       timezone: timeZone\r
+                               });\r
+                               job.save().then(function onJobCreated (result) {\r
+                                               logger.debug(JSON.stringify(result));\r
+                                               scheduleTestResponse.status = 200;\r
+                                               scheduleTestResponse.message = 'Successfully scheduled job.';\r
+                                               next();\r
+                                       })\r
+                                       .catch(function onError (error) {\r
+                                               logger.error(error);\r
+                                               scheduleTestResponse.status = 500;\r
+                                               scheduleTestResponse.message = 'Unable to schedule job.';\r
+                                               next();\r
+                                       });\r
+                       } else if (!testSchedule.testInstanceExecFreqInSeconds && !testSchedule.testInstanceEndDate) {\r
+                               agenda.schedule(\r
+                                       testSchedule._testInstanceStartDate,\r
+                                       'executeTestAsync',\r
+                                       { testSchedule, authorizationHeader })\r
+                                       .then(function onJobCreated (result) {\r
+                                               scheduleTestResponse.status = 200;\r
+                                               scheduleTestResponse.message = 'Successfully scheduled job.';\r
+                                               next();\r
+                                       })\r
+                                       .catch(function onError (error) {\r
+                                               logger.error('error: ' + error);\r
+                                               scheduleTestResponse.status = 500;\r
+                                               scheduleTestResponse.message = 'Unable to schedule job.';\r
+                                               next();\r
+                                       });\r
+                               return;\r
+                       } else if (testSchedule.testInstanceEndDate && !testSchedule.testInstanceExecFreqInSeconds) {\r
+                               scheduleTestResponse.status = 400;\r
+                               scheduleTestResponse.message = 'Must specify \'testInstanceExecFreqInSeconds\' to use \'testInstanceEndDate\'';\r
+\r
+                               next();\r
+                       }\r
+               }\r
+\r
+               if (!testSchedule.testInstanceStartDate &&\r
+                       !testSchedule.testInstanceExecFreqInSeconds &&\r
+                       !testSchedule.testInstanceExecFreqInSeconds) {\r
+                       agenda.now(\r
+                               'executeTestSync',\r
+                               { testSchedule, authorizationHeader })\r
+                               .then(function onJobCreated (result) {\r
+                                       emitter.once(result.attrs._id + '_error', (res) => {\r
+                                               logger.info(res);\r
+                                               scheduleTestResponse.message = res.message;\r
+                                               scheduleTestResponse.status = res.statusCode;\r
+                                               next();\r
+                                       });\r
+\r
+                                       emitter.once(result.attrs._id + '_ok', (res) => {\r
+                                               logger.info(res);\r
+                                               scheduleTestResponse.message = res;\r
+                                               scheduleTestResponse.status = 200;\r
+                                               next();\r
+                                       });\r
+                               })\r
+                               .catch(function onError (err) {\r
+                                       logger.error(err);\r
+\r
+                                       if (!Object.keys(scheduleTestResponse).includes('message')) {\r
+                                               scheduleTestResponse.message = 'Unknown error.';\r
+                                       }\r
+\r
+                                       if (!Object.keys(scheduleTestResponse).includes('status')) {\r
+                                               scheduleTestResponse.status = 500;\r
+                                       }\r
+                               });\r
+               }\r
+       }, (req, res) => {\r
+               res.type('json');\r
+               res.status(scheduleTestResponse.status).send(scheduleTestResponse);\r
+               logger.debug('Sent response with status %d and body %s', scheduleTestResponse.status, scheduleTestResponse.message);\r
+       });\r
+\r
+       // Cancel\r
+       app.use('/' + app.get('base-path') + 'cancel-test', (req, res, next) => {\r
+               // validate the request parameters\r
+               if (req.body === null) {\r
+                       cancelTestResponse.status = 400;\r
+                       cancelTestResponse.message = 'Request data is invalid.';\r
+\r
+                       next();\r
+                       return;\r
+               }\r
+\r
+               let requestBody = req.body;\r
+\r
+               if (!requestBody.jobId) {\r
+                       cancelTestResponse.status = 400;\r
+                       cancelTestResponse.message = 'jobId is required.';\r
+\r
+                       next();\r
+                       return;\r
+               }\r
+\r
+               if (!utils.isValidObjectId(requestBody.jobId)) {\r
+                       cancelTestResponse.status = 400;\r
+                       cancelTestResponse.message = 'jobId must be a valid ObjectId.';\r
+\r
+                       next();\r
+                       return;\r
+               }\r
+\r
+               if (!requestBody.executorId) {\r
+                       cancelTestResponse.status = 400;\r
+                       cancelTestResponse.message = 'executorId is required.';\r
+\r
+                       next();\r
+                       return;\r
+               }\r
+\r
+               if (!utils.isValidObjectId(requestBody.executorId)) {\r
+                       cancelTestResponse.status = 400;\r
+                       cancelTestResponse.message = 'executorId must be a valid ObjectId.';\r
+\r
+                       next();\r
+                       return;\r
+               }\r
+\r
+               const jobId = new ObjectId(requestBody.jobId);\r
+               const executorId = new ObjectId(requestBody.executorId);\r
+\r
+               agenda.cancel({ _id: jobId, 'data.testSchedule._executorId': executorId })\r
+                       .then(function onJobRemoved (numJobsRemoved) {\r
+                               logger.info('Number of jobs removed: %s', numJobsRemoved);\r
+\r
+                               cancelTestResponse.status = 200;\r
+                               cancelTestResponse.message = nodeUtil.format('Successfully removed job with Id %s', jobId);\r
+\r
+                               if (numJobsRemoved === 0) {\r
+                                       cancelTestResponse.status = 500;\r
+                                       cancelTestResponse.message =\r
+                                               nodeUtil.format('Unable to find job with Id %s, belonging to user with Id %s.', jobId, executorId);\r
+                               }\r
+\r
+                               next();\r
+                       })\r
+                       .catch(function onError (error) {\r
+                               logger.error(error.toString());\r
+\r
+                               cancelTestResponse.status = 500;\r
+                               cancelTestResponse.message = 'Unable to cancel the job due to an unexpected error.';\r
+\r
+                               next();\r
+                       });\r
+       }, (req, res) => {\r
+               res.type('json');\r
+               res.status(cancelTestResponse.status).send(cancelTestResponse);\r
+               logger.debug('Sent response with status %d and body %s', cancelTestResponse.status, cancelTestResponse.message);\r
+       });\r
+};\r
diff --git a/otf-frontend/server/src/agenda/jobs/test-execution-job.js b/otf-frontend/server/src/agenda/jobs/test-execution-job.js
new file mode 100644 (file)
index 0000000..b9a9198
--- /dev/null
@@ -0,0 +1,133 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const request = require('request');\r
+const requestPromise = require('request-promise');\r
+const logger = require('../../lib/logger');\r
+const emitter = require('../result-emitter').emitter;\r
+const config = require('config');\r
+\r
+const loggerTagExecuteTestSync = '[JOB-sync] ';\r
+const loggerTagExecuteTestAsync = '[JOB-async] ';\r
+\r
+module.exports = function (agenda) {\r
+       // [Job Definition] : Executes a testInstance synchronously.\r
+       agenda.define('executeTestSync', (job) => {\r
+               logger.debug(loggerTagExecuteTestSync + 'Running job %s.', job.attrs._id);\r
+\r
+               // Extact the testSchedule from the job data.\r
+               const testSchedule = job.attrs.data.testSchedule;\r
+\r
+               logger.debug('[POST-' +\r
+                       config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId + ']');\r
+\r
+               // Create and send the request\r
+               requestPromise.post({\r
+                       url: config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId,\r
+                       headers: {\r
+                               'Authorization': job.attrs.data.authorizationHeader,\r
+                               'Content-Type': 'application/json'\r
+                       },\r
+                       body: {\r
+                               'async': false,\r
+                               'executorId': testSchedule._executorId\r
+                       },\r
+                       json: true\r
+               }, function onResponseOk(response) {\r
+                       logger.debug('[POST-ok]: ' + JSON.stringify(response));\r
+                       emitter.emit(job.attrs._id + '_ok', response);\r
+               }, function onResponseError(response) {\r
+                       logger.debug('[POST-error]: ' + JSON.stringify(response));\r
+                       emitter.emit(job.attrs._id + '_error', response);\r
+               });\r
+       });\r
+\r
+       // [Job Definition] : Executes a testInstance asynchronously.\r
+       agenda.define('executeTestAsync', (job, done) => {\r
+               logger.debug(loggerTagExecuteTestAsync + 'Running job %s.', job.attrs._id);\r
+\r
+               // Extact the testSchedule from the job data.\r
+               const testSchedule = job.attrs.data.testSchedule;\r
+\r
+               if (testSchedule._testInstanceEndDate) {\r
+                       const currentDate = Date.now().valueOf();\r
+                       const endDate = Date.parse(testSchedule._testInstanceEndDate).valueOf();\r
+\r
+                       if (currentDate >= endDate) {\r
+                               job.remove(err => {\r
+                                       if (!err) {\r
+                                               logger.debug('Job %s finished.', job.attrs._id);\r
+                                       } else {\r
+                                               logger.error(err);\r
+                                       }\r
+                               });\r
+\r
+                               done();\r
+                               return;\r
+                       }\r
+               }\r
+\r
+               logger.debug('[POST-%s]', config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId);\r
+\r
+               // Create and send the request (we don't care about the response)\r
+               request.post({\r
+                       url: config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId,\r
+                       headers: {\r
+                               'Authorization': job.attrs.data.authorizationHeader,\r
+                               'Content-Type': 'application/json'\r
+                       },\r
+                       body: {\r
+                               'async': true,\r
+                               'executorId': testSchedule._executorId\r
+                       },\r
+                       json: true\r
+               }, function onResponseOk(response) {\r
+                       logger.debug('[POST-ok]: ' + JSON.stringify(response));\r
+                       emitter.emit(job.attrs._id + '_ok', response);\r
+               }, function onResponseError(response) {\r
+                       logger.debug('[POST-error]: ' + JSON.stringify(response));\r
+                       emitter.emit(job.attrs._id + '_error', response);\r
+               });\r
+\r
+               done();\r
+       });\r
+\r
+       agenda.define('executeTestOnInterval', (job, done) => {\r
+               logger.debug('[JOB-executeTestOnInterval] running...');\r
+\r
+               // Extact the testSchedule from the job data.\r
+               const testSchedule = job.attrs.data.testSchedule;\r
+\r
+               if (testSchedule._testInstanceEndDate) {\r
+                       if (new Date().now() > testSchedule._testInstanceEndDate) {\r
+                               job.remove((err) => {\r
+                                       if (err) {\r
+                                               logger.error(err); \r
+                                       }\r
+                               });\r
+                       }\r
+               }\r
+\r
+               logger.info('exec freq ' + testSchedule.testInstanceExecFreqInSeconds());\r
+\r
+               agenda.every(\r
+                       testSchedule._testInstanceExecFreqInSeconds + ' seconds',\r
+                       'executeTestAsync',\r
+                       {testSchedule: job.attrs.data.testSchedule, authorizationHeader: job.attrs.data.authorizationHeader});\r
+\r
+               done();\r
+       });\r
+};\r
diff --git a/otf-frontend/server/src/agenda/models/test-schedule.js b/otf-frontend/server/src/agenda/models/test-schedule.js
new file mode 100644 (file)
index 0000000..6494695
--- /dev/null
@@ -0,0 +1,154 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const logger = require('../../lib/logger');\r
+const utils = require('../../lib/otf-util');\r
+const ObjectId = require('mongoose').Types.ObjectId;\r
+\r
+class TestSchedule {\r
+       constructor (testInstanceId, testInstanceStartDate, testInstanceExecFreqInSeconds, testInstanceEndDate,\r
+                    async, asyncTopic, asyncMode, executorId) {\r
+               this.testInstanceId = testInstanceId;\r
+               this.testInstanceStartDate = testInstanceStartDate;\r
+               this.testInstanceExecFreqInSeconds = testInstanceExecFreqInSeconds;\r
+               this.testInstanceEndDate = testInstanceEndDate;\r
+               this.async = async;\r
+               this.asyncTopic = asyncTopic;\r
+               this.asyncMode = asyncMode;\r
+               this.executorId = executorId;\r
+       }\r
+\r
+       get testInstanceId () {\r
+               return this._testInstanceId;\r
+       }\r
+\r
+       set testInstanceId (value) {\r
+               if (!value) {\r
+                       throw 'testInstanceId is required.';\r
+               }\r
+\r
+               if (!utils.isValidObjectId(value)) {\r
+                       throw 'testInstanceId must be a valid ObjectId';\r
+               }\r
+\r
+               this._testInstanceId = new ObjectId(value);\r
+       }\r
+\r
+       get testInstanceStartDate () {\r
+               return this._testInstanceStartDate;\r
+       }\r
+\r
+       set testInstanceStartDate (value) {\r
+               // Accepts type Date, and the "now" keyword recognized by human interval (integrated with Agenda)\r
+               if (value !== 'now') {\r
+                       let parsedDate = Date.parse(value);\r
+\r
+                       if (isNaN((parsedDate))) {\r
+                               throw 'testInstanceStartDate must be a valid date, or must be ' / 'now' / '.';\r
+                       }\r
+               }\r
+\r
+               this._testInstanceStartDate = value;\r
+       }\r
+\r
+       get testInstanceExecFreqInSeconds () {\r
+               return this._testInstanceExecFreqInSeconds;\r
+       }\r
+\r
+       set testInstanceExecFreqInSeconds (value) {\r
+               if (value) {\r
+                       if (typeof value !== 'number') {\r
+                               throw 'testInstanceExecFreqInSeconds must be a number.';\r
+                       }\r
+\r
+                       if (value < 30) {\r
+                               throw 'testInstanceExecFreqInSeconds must be greater than or equal to 30.';\r
+                       }\r
+               }\r
+\r
+               this._testInstanceExecFreqInSeconds = value;\r
+       }\r
+\r
+       get testInstanceEndDate () {\r
+               return this._testInstanceEndDate;\r
+       }\r
+\r
+       set testInstanceEndDate (value) {\r
+               // Allow a null end date\r
+               if (value) {\r
+                       let parsedDate = Date.parse(value);\r
+\r
+                       if (isNaN((parsedDate))) {\r
+                               throw 'testInstanceEndDate must be a valid date.';\r
+                       }\r
+               }\r
+\r
+               this._testInstanceEndDate = value;\r
+       }\r
+\r
+       get async () {\r
+               return this._async;\r
+       }\r
+\r
+       set async (value) {\r
+               this._async = value;\r
+       }\r
+\r
+       get asyncTopic () {\r
+               return this._asynTopic;\r
+       }\r
+\r
+       set asyncTopic (value) {\r
+               this._asynTopic = value;\r
+       }\r
+\r
+       get asyncMode () {\r
+               return this._asyncMode;\r
+       }\r
+\r
+       set asyncMode (value) {\r
+               this._asyncMode = value;\r
+       }\r
+\r
+       get executorId () {\r
+               return this._executorId;\r
+       }\r
+\r
+       set executorId (value) {\r
+               if (!value) {\r
+                       throw 'executorId is required.';\r
+               }\r
+\r
+               if (!utils.isValidObjectId(value)) {\r
+                       throw 'executorId must be a valid ObjectId.';\r
+               }\r
+\r
+               this._executorId = new ObjectId(value);\r
+       }\r
+\r
+       print () {\r
+               logger.debug(\r
+                       'testInstanceId: ' + this._testInstanceId + '\n' +\r
+                       'testInstanceStartDate: ' + this._testInstanceStartDate + '\n' +\r
+                       'testInstanceExecFreqInSeconds: ' + this._testInstanceExecFreqInSeconds + '\n' +\r
+                       'testInstanceStartDate: ' + this._testInstanceStartDate + '\n' +\r
+                       'async: ' + this._async + '\n' +\r
+                       'asnycTopic: ' + this._asyncTopic + '\n' +\r
+                       'executorId: ' + this._executorId);\r
+       }\r
+}\r
+\r
+module.exports = TestSchedule;\r
diff --git a/otf-frontend/server/src/agenda/result-emitter.js b/otf-frontend/server/src/agenda/result-emitter.js
new file mode 100644 (file)
index 0000000..cdcf4af
--- /dev/null
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const events = require('events');\r
+const eventEmitter = new events.EventEmitter();\r
+\r
+module.exports.emitter = eventEmitter;\r
+\r
+// @author: rp978t\r
+/* @description: This module serves as a common emitter to be used by\r
+ * the test execution controller and job definitions. The Agenda library\r
+ * only returns a job object when that object persists in the database.\r
+ * Therefore, there is no conventional way to return the response from\r
+ * the service api (inside the job definition) to the controller to be\r
+ * sent back to the user of the scheduling endpoint (In this case it would\r
+ * be the UI). Setting up a listener in the controller will allow emitting\r
+ * an event from the job definition to relay the response. Events are\r
+ * distinguished by using the job identifier as part of the event name.\r
+ */\r
diff --git a/otf-frontend/server/src/app.js b/otf-frontend/server/src/app.js
new file mode 100644 (file)
index 0000000..37319bf
--- /dev/null
@@ -0,0 +1,101 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const cluster = require('cluster');\r
+const os = require('os');\r
+\r
+// Winston\r
+const logger = require('./lib/logger');\r
+\r
+const jobWorkers = [];\r
+const expressWorkers = [];\r
+\r
+process.env.NODE_CONFIG_DIR = './server/config';\r
+\r
+// TODO: Do we need this\r
+process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;\r
+\r
+const env = process.env['ENV'];\r
+const validEnvs = ['production', 'development', 'system_test', 'local'];\r
+\r
+if (env === undefined) {\r
+       logger.error('The process environment is not set, so the application will not start.\nPlease set the variable ' +\r
+               '\'%s\' to one of the following values: %s', 'env', validEnvs);\r
+       process.exit(1);\r
+} else if (!validEnvs.includes(env)) {\r
+       logger.error('%s is not a valid value.\nPlease set the environment variable \'%s\' to one of the following ' +\r
+               'values: %s', env, 'env', validEnvs);\r
+       process.exit(1);\r
+}\r
+\r
+// Workers can only be spawned on the master node.\r
+if (cluster.isMaster) {\r
+       // Use 8 CPU's on non-local environments, otherwise get the number of CPUs\r
+       const numWorkers = (env === 'local') ? 1 : 8;\r
+\r
+       logger.info('Master node is creating %d workers on the %s environment.', numWorkers, env);\r
+\r
+       // Spawns a worker process for every CPU\r
+       for (let i = 0; i < numWorkers; i++) {\r
+               addExpressWorker();\r
+               // Don't add job workers on local environments\r
+               if (env === 'local') continue;\r
+               addJobWorker();\r
+       }\r
+\r
+       // Listener for a spawned worker\r
+       cluster.on('exit', (worker, code, signal) => {\r
+               logger.info('Worker %d is online.', worker.process.pid);\r
+\r
+               if (jobWorkers.indexOf(worker.id) !== -1) {\r
+                       console.log(`job worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`);\r
+                       removeJobWorker(worker.id);\r
+                       addJobWorker();\r
+               }\r
+\r
+               if (expressWorkers.indexOf(worker.id) !== -1) {\r
+                       console.log(`express worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`);\r
+                       removeExpressWorker(worker.id);\r
+                       addExpressWorker();\r
+               }\r
+       });\r
+} else {\r
+       if (process.env.express) {\r
+               logger.info('Created express server process.');\r
+               require('./feathers/index');\r
+       }\r
+\r
+       if (process.env.job) {\r
+               logger.info('Created agenda job server process.');\r
+               require('./agenda/agenda').initializeAgenda();\r
+       }\r
+}\r
+\r
+function addExpressWorker () {\r
+       expressWorkers.push(cluster.fork({ express: 1 }).id);\r
+}\r
+\r
+function addJobWorker () {\r
+       jobWorkers.push(cluster.fork({ job: 1 }).id);\r
+}\r
+\r
+function removeExpressWorker (id) {\r
+       expressWorkers.splice(expressWorkers.indexOf(id), 1);\r
+}\r
+\r
+function removeJobWorker (id) {\r
+       jobWorkers.splice(jobWorkers.indexOf(id), 1);\r
+}\r
diff --git a/otf-frontend/server/src/feathers/app.hooks.js b/otf-frontend/server/src/feathers/app.hooks.js
new file mode 100644 (file)
index 0000000..4cd08ac
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Application hooks that run for every service\r
+const log = require('./hooks/log');\r
+const paginateOption = require('./hooks/paginate-option');\r
+const createdBy = require('./hooks/createdBy');\r
+const updatedBy = require('./hooks/updatedBy');\r
+const {iff, disallow, isProvider, skipRemainingHooks} = require('feathers-hooks-common');\r
+const { ObjectID } = require('mongodb');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [paginateOption(), skipRemainingHooks(context => !context.params.provider)],\r
+               find: [\r
+                       function(context){\r
+                               const {query} = context.params;\r
+\r
+                               iterate(query, '');\r
+\r
+                               return context;\r
+                       }\r
+               ],\r
+               get: [],\r
+               create: [createdBy()],\r
+               update: [updatedBy()],\r
+               patch: [updatedBy()],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [log()],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
+\r
+function iterate(obj, stack) {\r
+       for (var property in obj) {\r
+               if (obj.hasOwnProperty(property)) {\r
+                       if (typeof obj[property] == "object") {\r
+\r
+                               //check for $in\r
+                               if(/\._id$/.test(property) && obj[property]['$in'] && obj[property]['$in'].length && obj[property]['$in'].length > 0){\r
+                                       obj[property]['$in'].forEach((elem, val) => {\r
+                                               obj[property]['$in'][val] = new ObjectID(elem);\r
+                                       })\r
+                               }else{\r
+                                       iterate(obj[property], stack + '.' + property);\r
+                               }\r
+                       } else if(/\._id$/.test(property)){\r
+                               obj[property] = new ObjectID(obj[property]);\r
+                       }\r
+               }\r
+       }\r
+}\r
diff --git a/otf-frontend/server/src/feathers/authentication.js b/otf-frontend/server/src/feathers/authentication.js
new file mode 100644 (file)
index 0000000..2a0efc9
--- /dev/null
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const authentication = require('@feathersjs/authentication');\r
+const jwt = require('@feathersjs/authentication-jwt');\r
+const local = require('@feathersjs/authentication-local');\r
+const { permissions } = require('./hooks/permissions/permissions');\r
+// const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks;\r
+\r
+module.exports = function (app) {\r
+       const config = app.get('authentication');\r
+\r
+       // Set up authentication with the secret\r
+       app.configure(authentication(config));\r
+       app.configure(jwt());\r
+       app.configure(local());\r
+\r
+       // The `authentication` service is used to create a JWT.\r
+       // The before `create` hook registers strategies that can be used\r
+       // to create a new valid JWT (e.g. local or oauth2)\r
+       app.service(config.path).hooks({\r
+               before: {\r
+                       create: [\r
+                               function(context){\r
+                                        //console.log(context.data)\r
+                                       // console.log('authing');\r
+                               },\r
+                               authentication.hooks.authenticate(config.strategies),\r
+                               permissions('authentication')\r
+                       ],\r
+                       remove: [\r
+                               authentication.hooks.authenticate('jwt')\r
+                       ]\r
+               },\r
+               after: {\r
+                       create: [\r
+                               // Send the user profile back with access token\r
+                               async function (context) {\r
+                                       if (!context.params.user.enabled) {\r
+                                               context.result.accessToken = null;\r
+                                       }\r
+\r
+                                       context.result['user'] = context.params.user;\r
+\r
+                                       //Send Back the users rules\r
+                                       if(context.params.ability){\r
+                                               context.result.user['rules'] = context.params.ability.rules;\r
+                                       }\r
+\r
+                                       delete context.result.user.password;\r
+                                       return context;\r
+                               }\r
+                       ]\r
+               }\r
+       });\r
+};\r
diff --git a/otf-frontend/server/src/feathers/channels.js b/otf-frontend/server/src/feathers/channels.js
new file mode 100644 (file)
index 0000000..a896480
--- /dev/null
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function (app) {\r
+       if (typeof app.channel !== 'function') {\r
+               // If no real-time functionality has been configured just return\r
+               return;\r
+       }\r
+\r
+       app.on('connection', connection => {\r
+               // On a new real-time connection, add it to the anonymous channel\r
+               app.channel('anonymous').join(connection);\r
+               \r
+       });\r
+\r
+       app.on('login', (authResult, { connection }) => {\r
+               // connection can be undefined if there is no\r
+               // real-time connection, e.g. when logging in via REST\r
+               if (connection) {\r
+                       // Obtain the logged in user from the connection\r
+                       // const user = connection.user;\r
+\r
+                       // The connection is no longer anonymous, remove it\r
+                       app.channel('anonymous').leave(connection);\r
+\r
+                       // Add it to the authenticated user channel\r
+                       app.channel('authenticated').join(connection);\r
+\r
+                       // Channels can be named anything and joined on any condition\r
+\r
+                       // E.g. to send real-time events only to admins use\r
+                       // if(user.isAdmin) { app.channel('admins').join(connection); }\r
+\r
+                       // If the user has joined e.g. chat rooms\r
+                       // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel));\r
+\r
+                       // Easily organize users by email and userid for things like messaging\r
+                       // app.channel(`emails/${user.email}`).join(channel);\r
+                       // app.channel(`userIds/$(user.id}`).join(channel);\r
+               }\r
+       });\r
+\r
+       //eslint-disable-next-line no-unused-vars\r
+       app.publish((data, hook) => {\r
+               // Here you can add event publishers to channels set up in `channels.js`\r
+               // To publish only for a specific event use `app.publish(eventname, () => {})`\r
+\r
+               console.log('Publishing all events to all authenticated users. See `channels.js` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line\r
+\r
+               // e.g. to publish all service events to all authenticated users use\r
+               return app.channel('authenticated');\r
+       });\r
+\r
+       // Here you can also add service specific event publishers\r
+       // e.g. the publish the `users` service `created` event to the `admins` channel\r
+       // app.service('users').publish('created', () => app.channel('admins'));\r
+\r
+       // With the userid and email organization from above you can easily select involved users\r
+       // app.service('messages').publish(() => {\r
+       //   return [\r
+       //     app.channel(`userIds/${data.createdBy}`),\r
+       //     app.channel(`emails/${data.recipientEmail}`)\r
+       //   ];\r
+       // });\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/agendaJobPopulate.js b/otf-frontend/server/src/feathers/hooks/agendaJobPopulate.js
new file mode 100644 (file)
index 0000000..51cea24
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function () {\r
+       return async context => {\r
+               if (context.result.data && context.result.limit >= 0 ) {\r
+                       for (let i = 0; i < context.result.data.length; i++) {\r
+                               await context.app.services[context.app.get('base-path') + 'test-instances']\r
+                                       .get(context.result.data[i].data.testSchedule._testInstanceId, context.params)\r
+                                       .then(result => {\r
+                                               context.result.data[i].testInstanceName = result.testInstanceName;\r
+                                       })\r
+                                       .catch(err => {\r
+                                               console.log(err);\r
+                                       });\r
+                       }\r
+               } else if(context.result.limit) {\r
+                       await context.app.services[context.app.get('base-path') + 'test-instances']\r
+                               .get(context.result.data.data.testSchedule._testInstanceId, context.params)\r
+                               .then(result => {\r
+                                       context.result.data.testInstanceName = result.testInstanceName;\r
+                               })\r
+                               .catch(err => {\r
+                                       console.log(err);\r
+                               });\r
+               }else if (context.result.length) {\r
+                       for (let i = 0; i < context.result.length; i++) {\r
+                               await context.app.services[context.app.get('base-path') + 'test-instances']\r
+                                       .get(context.result[i].data.testSchedule._testInstanceId, context.params)\r
+                                       .then(result => {\r
+                                               context.result[i].testInstanceName = result.testInstanceName;\r
+                                       })\r
+                                       .catch(err => {\r
+                                               console.log(err);\r
+                                       });\r
+                       }\r
+               } else if(context.result.data) {\r
+                       await context.app.services[context.app.get('base-path') + 'test-instances']\r
+                               .get(context.result.data.testSchedule._testInstanceId, context.params)\r
+                               .then(result => {\r
+                                       context.result.testInstanceName = result.testInstanceName;\r
+                               })\r
+                               .catch(err => {\r
+                                       console.log(err);\r
+                               });\r
+               }\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/checkLocks.js b/otf-frontend/server/src/feathers/hooks/checkLocks.js
new file mode 100644 (file)
index 0000000..b4d527e
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function () {\r
+    return async context => {\r
+            return await context.app.services[context.app.get('base-path') + 'test-instances']\r
+                .get(context.data.testInstanceId, context.params)\r
+                .then( result => {\r
+                    if(result.disabled === true){\r
+                        return true;\r
+                        //throw new Error('Test Instance is locked and cannot be run!');\r
+                    }else{\r
+                        return context.app.services[context.app.get('base-path') + 'test-definitions']\r
+                            .get(result.testDefinitionId, context.params)\r
+                            .then(results => {\r
+                                if(results.disabled === true){\r
+                                    return true;\r
+                                    \r
+                                    //throw new Error('Test Definition is locked! The instance can not be run!');\r
+                                }else{\r
+                                    return false;\r
+                                }\r
+                            })\r
+                            .catch(err => {\r
+                                console.log(err);\r
+                            });\r
+                    }\r
+                    \r
+                })\r
+                .catch(err => {\r
+                    console.log(err);\r
+                });\r
+                \r
+            }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/hooks/checkPermissions.js b/otf-frontend/server/src/feathers/hooks/checkPermissions.js
new file mode 100644 (file)
index 0000000..fb02fa3
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function () {\r
+    return async context => {\r
+               let group = {};\r
+               if(context.data.parentGroupId){\r
+                       //get the groups from the group service\r
+                       //check if the user is an Admin in the parent group \r
+                       await context.app.services[context.app.get('base-path') + 'groups']\r
+                       .get(context.data.parentGroupId, context.params)\r
+                       .then( result => {      \r
+                               group = result;\r
+                       });\r
+                       \r
+                       if(group.members){\r
+                               for(let i = 0; i < group.members.length; i++){\r
+                                       if(group.members[i].userId.toString() === context.params.user._id.toString()){\r
+                                               if(!group.members[i].roles.includes("admin")){\r
+                                                       throw new Error('Can not create child group. You must be an admin of the parent group.');\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }else{\r
+                               throw new Error('Can not create child group. You must be an admin of the parent group.');\r
+                       }\r
+               }\r
+       }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/hooks/convertToJSON.js b/otf-frontend/server/src/feathers/hooks/convertToJSON.js
new file mode 100644 (file)
index 0000000..0a20378
--- /dev/null
@@ -0,0 +1,142 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const YAML = require('yamljs');\r
+\r
+module.exports = function () {\r
+    function convertTabs(str){\r
+        return str.replace(/\t/g, '    ');\r
+    }\r
+       return async context => {\r
+               \r
+               if (context.data.length) {\r
+                       for (var i = 0; i < context.data.length; i++) {\r
+                               if (context.data[i].testData && typeof '' === typeof context.data[i].testData) {\r
+                    context.data[i].testData = convertTabs(context.data[i].testData);\r
+                                       context.data[i].testData = YAML.parse(context.data[i].testData);\r
+                               }\r
+                               if (context.data[i].testConfig) {\r
+                    context.data[i].testConfig = convertTabs(context.data[i].testConfig);\r
+                                       context.data[i].testConfig = YAML.parse(context.data[i].testConfig);\r
+                               }\r
+                               if (context.data[i].testConfigTemplate) {\r
+                    context.data[i].testConfigTemplate = convertTabs(context.data[i].testConfigTemplate);\r
+                                       context.data[i].testConfigTemplate = YAML.parse(context.data[i].testConfigTemplate);\r
+                               }\r
+                               if (context.data[i].vthInputTemplate && typeof '' === typeof context.data[i].vthInputTemplate) {\r
+                                       context.data[i].vthInputTemplate = convertTabs(context.data[i].vthInputTemplate);\r
+                                       context.data[i].vthInputTemplate = YAML.parse(context.data[i].vthInputTemplate);\r
+                               }\r
+\r
+                //Set empty string as empty json\r
+                if (context.data[i].testData == ''){\r
+                    context.data[i].testData = {};\r
+                }\r
+                if (context.data[i].testConfig == ''){\r
+                    context.data[i].testConfig = {};\r
+                }\r
+                               if (context.data[i].testConfigTemplate == '') {\r
+                    context.data[i].testConfigTemplate = {};\r
+                               }\r
+                               if(context.data[i].bpmnInstances){\r
+                                       for(let k in context.data[i].bpmnInstances){\r
+                                               let key = context.data[i].bpmnInstances[k];\r
+                                               if(key.testHeads){\r
+                                                       for(let h in key.testHeads){\r
+                                                               let head = key.testHeads[h];\r
+                                                               if(head.testHeadId.vthInputTemplate == ''){\r
+                                                                       head.testHeadId.vthInputTemplate = {};\r
+                                                               }else if(head.testHeadId.vthInputTemplate && typeof '' === typeof head.testHeadId.vthInputTemplate){\r
+                                                                       head.testHeadId.vthInputTemplate = YAML.parse(head.testHeadId.vthInputTemplate);\r
+                                                               }\r
+                                                       }\r
+                                               }\r
+                                               if(key.testDataTemplate == ''){ \r
+                                                       key.testDataTemplate = {};\r
+                                               }else if(typeof '' === typeof key.testDataTemplate){\r
+                                                       key.testDataTemplate = YAML.parse(key.testDataTemplate);\r
+                                               }       \r
+                                       }\r
+                               }\r
+                       }\r
+               } else {\r
+                       if (context.data.testData && typeof '' === typeof context.data.testData) {\r
+                context.data.testData = convertTabs(context.data.testData);\r
+                               context.data.testData = YAML.parse(context.data.testData);\r
+                       }\r
+                       if (context.data.testConfig) {\r
+                context.data.testConfig = convertTabs(context.data.testConfig);\r
+                               context.data.testConfig = YAML.parse(context.data.testConfig);\r
+                       }\r
+                       if (context.data.testConfigTemplate) {\r
+                context.data.testConfigTemplate = convertTabs(context.data.testConfigTemplate);\r
+                               context.data.testConfigTemplate = YAML.parse(context.data.testConfigTemplate);\r
+                       }\r
+                       if (context.data.vthInputTemplate && typeof '' === typeof context.data.vthInputTemplate) {\r
+                context.data.vthInputTemplate = convertTabs(context.data.vthInputTemplate);\r
+                               context.data.vthInputTemplate = YAML.parse(context.data.vthInputTemplate);\r
+                       }\r
+                       if (context.data.vthInput){\r
+                               for(let k in context.data.vthInput){\r
+                                       if(typeof context.data.vthInput[k] === typeof ''){\r
+                                               context.data.vthInput[k] = YAML.parse(context.data.vthInput[k]);\r
+                                               if(context.data.vthInput[k] === null)\r
+                                                       context.data.vthInput[k] = {};\r
+                                       }\r
+                               }\r
+                       }\r
+                       if(context.data.bpmnInstances){\r
+                               for(let k in context.data.bpmnInstances){\r
+                                       let key = context.data.bpmnInstances[k];\r
+                                       if(key.testHeads){\r
+                                               for(let h in key.testHeads){\r
+                                                       let head = key.testHeads[h];\r
+                                                       if(head.testHeadId.vthInputTemplate == ''){\r
+                                                               head.testHeadId.vthInputTemplate = {};\r
+                                                       }else if(head.testHeadId.vthInputTemplate && typeof '' === typeof head.testHeadId.vthInputTemplate){\r
+                                                               head.testHeadId.vthInputTemplate = YAML.parse(head.testHeadId.vthInputTemplate);\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                                       if(key.testDataTemplate == ''){ \r
+                                               key.testDataTemplate = {};\r
+                                       }else  if(typeof '' === typeof key.testDataTemplate){\r
+                                               key.testDataTemplate = YAML.parse(key.testDataTemplate);\r
+                                       }       \r
+                               }\r
+                       }\r
+                       //Set empty string as empty json\r
+            if (context.data.testData == ''){\r
+                context.data.testData = {};\r
+            }\r
+            if (context.data.testConfig == ''){\r
+                context.data.testConfig = {};\r
+            }\r
+            if (context.data.testConfigTemplate == '') {\r
+                context.data.testConfigTemplate = {};\r
+                       }\r
+                       if (context.data.vthInputTemplate == '') {\r
+                               context.data.vthInputTemplate = {};\r
+                       }\r
+               }\r
+               // for(var j = 0; j < context.data.length; j++){\r
+               //     for(var i = 0; i < context.data[j].testData.length; i++){\r
+               //          context.data[j].testData[i].testData = YAML.parse(context.data[j].testData[i].testData);\r
+               //     }\r
+               // }\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/convertToYAML.js b/otf-frontend/server/src/feathers/hooks/convertToYAML.js
new file mode 100644 (file)
index 0000000..6c777d0
--- /dev/null
@@ -0,0 +1,335 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const YAML = require('yamljs');\r
+\r
+module.exports = function () {\r
+       function isEmpty(obj) {\r
+               for(var key in obj) {\r
+                       if(obj.hasOwnProperty(key))\r
+                               return false;\r
+               }\r
+               return true;\r
+       }\r
+\r
+       return async context => {\r
+               \r
+               if (context.result.length) {\r
+                       for (let i = 0; i < context.result.length; i++) {\r
+                               if (context.result[i].testData) {\r
+                                       if(isEmpty(context.result[i].testData)){\r
+                                               context.result[i].testDataJSON = {};\r
+                                               context.result[i].testData = '';\r
+                                       }else if(typeof context.result[i].testDate != typeof ''){\r
+                                               context.result[i].testDataJSON = context.result[i].testData;\r
+                                               context.result[i].testData = YAML.stringify(context.result[i].testData);\r
+                                       }       \r
+                               }\r
+                               if (context.result[i].testConfig) {\r
+                                       if(isEmpty(context.result[i].testConfig)){      \r
+                                               context.result[i].testConfig = '';\r
+                                       }else{\r
+                                               context.result[i].testConfig = YAML.stringify(context.result[i].testConfig);\r
+                                       }       \r
+                               }\r
+                               if(context.result[i].vthInputTemplate){\r
+                                       if(isEmpty(context.result[i].vthInputTemplate)){\r
+                                               context.result[i].vthInputTemplate = '';\r
+                                       }else{\r
+                                               if(typeof '' !== typeof context.result[i].vthInputTemplate){\r
+                                                       context.result[i].vthInputTemplateJSON = context.result[i].vthInputTemplate;\r
+                                                       context.result[i].vthInputTemplate = YAML.stringify(context.result[i].vthInputTemplate);\r
+                                               }\r
+                                       }\r
+                               }\r
+                               if (context.result[i].vthInput) {\r
+                                       context.result[i].vthInputYaml = '';\r
+                                       if(isEmpty(context.result[i].vthInput)){\r
+                                               context.result[i].vthInput = {};\r
+                                       }else{\r
+                                               context.result[i].vthInputYaml = {};\r
+                                               for(key in context.result[i].vthInput){\r
+                                                       \r
+                                                       context.result[i].vthInputYaml[key] = YAML.stringify(context.result[i].vthInput[key]);\r
+                                               }\r
+                                       }\r
+                                       \r
+                               }\r
+                               if (context.result[i].bpmnInstances) {\r
+                                       \r
+                                       //console.log("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH");\r
+                                       // if(context.re)\r
+                                       //      if(isEmpty(context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate)){\r
+                                       //              context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate = '';\r
+                                       //      }else{\r
+                                       //              context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplateJSON = context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate;\r
+                                       //              context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate = YAML.stringify(context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate);\r
+                                       //      }\r
+                                       \r
+                               \r
+                                       for(let k = 0; k < context.result[i].bpmnInstances.length; k++){\r
+                                               \r
+                                               if( context.result[i].bpmnInstances[k].testHeads){\r
+                                                       for(let h = 0; h <  context.result[i].bpmnInstances[k].testHeads.length; h++){\r
+                                                               //let head = key.testHeads[h];\r
+                                                               //console.log("___________________________________________________________");\r
+                                                               //console.log(context.result[i].bpmnInstances[k].testHeads[h].testHeadId);\r
+                                                               if(typeof '' != typeof context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){\r
+                                                                       if(isEmpty(context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate)){\r
+                                                                               context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplateJSON = {};\r
+                                                                               context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate = '';\r
+                                                                       }else if(context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){\r
+                                                                               context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplateJSON = context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate;\r
+                                                                               context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate = YAML.stringify(context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate);\r
+                                                                       }\r
+                                                               }\r
+                                                       }\r
+                                               }\r
+                                               if(isEmpty(context.result[i].bpmnInstances[k].testDataTemplate)){       \r
+                                                       context.result[i].bpmnInstances[k].testDataTemplate = '';\r
+                                                       context.result[i].bpmnInstances[k].testDataTemplateJSON = {};\r
+                                               }else if(typeof context.result[i].bpmnInstances[k].testDataTemplate !== typeof ''){\r
+                                                       context.result[i].bpmnInstances[k].testDataTemplateJSON = context.result[i].bpmnInstances[k].testDataTemplate;\r
+                                                       context.result[i].bpmnInstances[k].testDataTemplate = YAML.stringify(context.result[i].bpmnInstances[k].testDataTemplate);\r
+                                               }       \r
+                                               //context.result[i].bpmnInstances[k] = key;\r
+                                       }\r
+                                       \r
+                               }\r
+                       }\r
+               } else if (context.result.total > 1) {\r
+                       for (let i = 0; i < context.result.data.length; i++) {\r
+                               if (context.result.data[i].testData) {\r
+                                       if(isEmpty(context.result.data[i].testData)){\r
+                                               context.result.data[i].testData = '';\r
+                                               context.result.data[i].testDataJSON = {};\r
+                                       }else if(typeof context.result.data[i].testDate != typeof ''){\r
+                                               context.result.data[i].testDataJSON = context.result.data[i].testData;\r
+                                               context.result.data[i].testData = YAML.stringify(context.result.data[i].testData);\r
+                                       }\r
+                                       \r
+                               }\r
+                               if (context.result.data[i].vthInput) {\r
+                                       context.result.data[i].vthInputYaml = '';\r
+                                       if(isEmpty(context.result.data[i].vthInput)){\r
+                                               context.result.data[i].vthInput = {};\r
+                                       }else{\r
+                                               context.result.data[i].vthInputYaml = {};\r
+                                               for(key in context.result.data[i].vthInput){\r
+                                                       \r
+                                                       context.result.data[i].vthInputYaml[key] = YAML.stringify(context.result.data[i].vthInput[key]);\r
+                                               }\r
+                                       }\r
+                                       \r
+                               }\r
+                               if (context.result.data[i].testConfig) {\r
+                                       if(isEmpty(context.result.data[i].testConfig)){\r
+                                               context.result.data[i].testConfig = '';\r
+                                       }else{\r
+                                               context.result.data[i].testConfig = YAML.stringify(context.result.data[i].testConfig);\r
+                                       }\r
+                               }\r
+                               if (context.result.data[i].testDataTemplate) {\r
+                                       if(isEmpty(context.result.data[i].testDataTemplate)){\r
+                                               context.result.data[i].testDataTemplate = '';\r
+                                               context.result.data[i].testDataTemplateJSON = {};\r
+                                       }else{\r
+                                               context.result.data[i].testDataTemplateJSON = context.result.data[i].testDataTemplate;\r
+                                               context.result.data[i].testDataTemplate = YAML.stringify(context.result.data[i].testDataTemplate);\r
+                                       }\r
+                               }\r
+                               if(context.result.data[i].bpmnInstances){\r
+                                       for(let k in context.result.data[i].bpmnInstances){\r
+                                               let key = context.result.data[i].bpmnInstances[k];\r
+                                               if(key.testHeads){\r
+                                                       for(let h in key.testHeads){\r
+                                                               let head = key.testHeads[h];\r
+                                                               \r
+                                                               if(typeof '' != typeof context.result.data[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){       \r
+                                                                       if(isEmpty(head.testHeadId.vthInputTemplate)){\r
+                                                                               head.testHeadId.vthInputTemplate = '';\r
+                                                                               head.testHeadId.vthInputTemplateJSON = {};\r
+                                                                       }else if(head.testHeadId.vthInputTemplate){\r
+                                                                               head.testHeadId.vthInputTemplateJSON = head.testHeadId.vthInputTemplate;\r
+                                                                               head.testHeadId.vthInputTemplate = YAML.stringify(head.testHeadId.vthInputTemplate);\r
+                                                                       }\r
+                                                               }\r
+                                                               key.testHeads[h] = head;\r
+                                                       }\r
+                                               }\r
+                                               if(isEmpty(key.testDataTemplate)){      \r
+                                                       key.testDataTemplate = '';\r
+                                                       key.testDataTemplateJSON = {};\r
+                                               }else if(typeof key.testDataTemplate !== typeof ''){\r
+                                                       key.testDataTemplateJSON = key.testDataTemplate;\r
+                                                       key.testDataTemplate = YAML.stringify(key.testDataTemplate);\r
+                                               }\r
+                                               context.result.data[i].bpmnInstances[k] = key;  \r
+                                       }\r
+                               }\r
+                       }\r
+               } else if (context.result.data) {\r
+                       if (context.result.data.testData) {\r
+                               if(isEmpty(context.result.data.testData)){\r
+                                       context.result.data.testData = '';\r
+                                       context.result.data.testDataJSON = {};\r
+                               }else if(typeof context.result.data.testDate != typeof ''){\r
+                                       context.result.data.testDataJSON = context.result.data.testData;\r
+                                       context.result.data.testData = YAML.stringify(context.result.data.testData);\r
+                               }       \r
+                       }\r
+                       if (context.result.data.testConfig) {\r
+                               if(isEmpty(context.result.data.testConfig)){\r
+                                       context.result.data.testConfig = '';\r
+                               }else{\r
+                                       context.result.data.testConfig = YAML.stringify(context.result.data.testConfig);\r
+                               }       \r
+                       }\r
+                       if (context.result.data.vthInput) {\r
+                               context.result.data.vthInputYaml = '';\r
+                               if(isEmpty(context.result.data.vthInput)){\r
+                                       context.result.data.vthInput = {};\r
+                               }else{\r
+                                       context.result.data.vthInputYaml = {};\r
+                                       for(key in context.result.data.vthInput){\r
+                                               context.result.data.vthInputYaml[key] = YAML.stringify(context.result.data.vthInput[key]);\r
+                                       }\r
+                               }\r
+                               \r
+                       }\r
+                       if (context.result.data.testDataTemplate) {\r
+                               if(isEmpty(context.result.data.testDataTemplate)){\r
+                                       context.result.data.testDataTemplate = '';\r
+                                       context.result.data.testDataTemplateJSON = {};\r
+                               }else{\r
+                                       context.result.data.testDataTemplateJSON = context.result.data.testDataTemplate;\r
+                                       context.result.data.testDataTemplate = YAML.stringify(context.result.data.testDataTemplate);\r
+                               }               \r
+                       }\r
+                       if (context.result.data.bpmnInstances){\r
+                               for(let k in context.result.data.bpmnInstances){\r
+                                       let key = context.result.data.bpmnInstances[k];\r
+                                       if(key.testHeads){\r
+                                               for(let h in key.testHeads){\r
+                                                       let head = key.testHeads[h];\r
+                                                       //console.log(head.testHeadId);\r
+                                                       if(typeof '' != typeof context.result.data.bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){\r
+                                                               if(isEmpty(head.testHeadId.vthInputTemplate)){\r
+                                                                       head.testHeadId.vthInputTemplate = '';\r
+                                                                       head.testHeadId.vthInputTemplateJSON = {};\r
+                                                               }else if(head.testHeadId.vthInputTemplate){\r
+                                                                       head.testHeadId.vthInputTemplateJSON = head.testHeadId.vthInputTemplate;\r
+                                                                       head.testHeadId.vthInputTemplate = YAML.stringify(head.testHeadId.vthInputTemplate);\r
+                                                               }\r
+                                                       }\r
+                                                       key.testHeads[h] = head;\r
+                                               }\r
+                                       }\r
+                                       if(isEmpty(key.testDataTemplate)){      \r
+                                               key.testDataTemplate = '';\r
+                                               key.testDataTemplateJSON = {};\r
+                                       }else if(typeof key.testDataTemplate !== typeof ''){\r
+                                               key.testDataTemplateJSON = key.testDataTemplate;\r
+                                               key.testDataTemplate = YAML.stringify(key.testDataTemplate);\r
+                                       }\r
+                                       context.result.data.bpmnInstances[k] = key;     \r
+                               }\r
+                       }\r
+               } else {\r
+                       if (context.result.testData) {\r
+                               \r
+                               if(isEmpty(context.result.testData)){\r
+                                       context.result.testData = '';\r
+                                       context.result.testDataJSON = {};\r
+                               }else if(typeof context.result.testData != typeof ''){\r
+                                       context.result.testDataJSON = context.result.testData;\r
+                                       context.result.testData = YAML.stringify(context.result.testData);\r
+                               }else if(typeof context.result.testData == typeof ''){\r
+                                       context.result.testDataJSON = YAML.parse(context.result.testData);\r
+                               }\r
+                               \r
+                       }\r
+                       if (context.result.testConfig) {\r
+                               if(isEmpty(context.result.testConfig)){\r
+                                       context.result.testConfig = '';\r
+                               }else{\r
+                                       context.result.testConfig = YAML.stringify(context.result.testConfig);\r
+                               }\r
+                       }\r
+                       if (context.result.vthInput) {\r
+                               context.result.vthInputYaml = '';\r
+                               if(isEmpty(context.result.vthInput)){\r
+                                       context.result.vthInput = {};\r
+                               }else{\r
+                                       context.result.vthInputYaml = {};\r
+                                       for(key in context.result.vthInput){\r
+                                               context.result.vthInputYaml[key] = YAML.stringify(context.result.vthInput[key]);\r
+                                       }\r
+                               }\r
+                               \r
+                       }\r
+                       if (context.result.testDataTemplate) {\r
+                               if(isEmpty(context.result.testDataTemplate)){\r
+                                       context.result.testDataTemplate = '';\r
+                                       context.result.testDataTemplateJSON = {};\r
+                               }else{\r
+                                       context.result.testDataTemplateJSON = context.result.testDataTemplate;\r
+                                       context.result.testDataTemplate = YAML.stringify(context.result.testDataTemplate);\r
+                               }\r
+                       }\r
+                       if(context.result.vthInputTemplate){\r
+                               if(isEmpty(context.result.vthInputTemplate)){\r
+                                       context.result.vthInputTemplate = '';\r
+                                       context.result.vthInputTemplateJSON = {};\r
+                               }else{\r
+                                       context.result.vthInputTemplateJSON = context.result.vthInputTemplate;\r
+                                       context.result.vthInputTemplate = YAML.stringify(context.result.vthInputTemplate);\r
+                               }\r
+                       }\r
+                       if(context.result.bpmnInstances){\r
+                               for(let k in context.result.bpmnInstances){\r
+                                       let key = context.result.bpmnInstances[k];\r
+                                       if(key.testHeads){\r
+                                               for(let h in key.testHeads){\r
+                                                       let head = key.testHeads[h];\r
+                                                       //console.log(head.testHeadId);\r
+                                                       if(typeof '' != typeof context.result.bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){\r
+                                                               if(isEmpty(head.testHeadId.vthInputTemplate)){\r
+                                                                       head.testHeadId.vthInputTemplate = '';\r
+                                                                       head.testHeadId.vthInputTemplateJSON = {};\r
+                                                               }else if(head.testHeadId.vthInputTemplate){\r
+                                                                       head.testHeadId.vthInputTemplateJSON = head.testHeadId.vthInputTemplate;\r
+                                                                       head.testHeadId.vthInputTemplate = YAML.stringify(head.testHeadId.vthInputTemplate);\r
+                                                               }\r
+                                                       }\r
+                                                       key.testHeads[h] = head;\r
+                                               }\r
+                                       }\r
+                                       if(isEmpty(key.testDataTemplate)){      \r
+                                               key.testDataTemplate = '';\r
+                                               key.testDataTemplateJSON = {};\r
+                                       }else if(typeof key.testDataTemplate !== typeof ''){\r
+                                               key.testDataTemplateJSON = key.testDataTemplate;\r
+                                               key.testDataTemplate = YAML.stringify(key.testDataTemplate);\r
+                                       }       \r
+                                       context.result.bpmnInstances[k] = key;\r
+                               }\r
+                       }\r
+               \r
+               }\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/convertToYAMLRecursive.js b/otf-frontend/server/src/feathers/hooks/convertToYAMLRecursive.js
new file mode 100644 (file)
index 0000000..67b871a
--- /dev/null
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const YAML = require('yamljs');\r
+\r
+module.exports = function (convertTo) {\r
+\r
+       const toConvert = ['testDataTemplate'];\r
+\r
+       function convert(p) {\r
+               for (var key in p) {\r
+                       if (p.hasOwnProperty(key) && (typeof p[key] === 'object' || typeof p[key] === 'array') ) {\r
+                               if (toConvert.indexOf(key) < 0) {\r
+                                       convert(p[key])\r
+                               } else {\r
+                                       \r
+                                       if(convertTo == 'yaml'){\r
+                                               p[key] = YAML.stringify(p[key]);\r
+                                       }\r
+                                       if(convertTo == 'json'){\r
+                                               console.log(key)\r
+                                               console.log(p[key]);\r
+                                               p[key] = convertTabs(p[key]);\r
+                                               p[key] = YAML.parse(p[key]);\r
+                                               console.log(p[key]);\r
+                                       }\r
+                               }\r
+                       }else{\r
+                               if (toConvert.indexOf(key) >= 0) {\r
+                                               \r
+                                       if(convertTo == 'yaml'){\r
+                                               p[key] = YAML.stringify(p[key]);\r
+                                       }\r
+                                       if(convertTo == 'json'){\r
+                                               p[key] = convertTabs(p[key]);\r
+                                               p[key] = YAML.parse(p[key]);\r
+                                               console.log(p[key])\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       function convertTabs(str){\r
+               if(typeof str === 'string'){\r
+                       return str.replace(/\t/g, '    ');\r
+               }\r
+    }\r
+\r
+       return async context => {\r
+               if(context.result)\r
+                       convert(context.result);\r
+               if(context.data)\r
+                       convert(context.data);\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/createdBy.js b/otf-frontend/server/src/feathers/hooks/createdBy.js
new file mode 100644 (file)
index 0000000..537db25
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function () {\r
+       return async context => {\r
+        if(!context.data){\r
+            context.data = {};\r
+        }\r
+        if(context.params.user){\r
+            context.data.createdBy = context.params.user._id;\r
+        }\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/delete-definition.js b/otf-frontend/server/src/feathers/hooks/delete-definition.js
new file mode 100644 (file)
index 0000000..718abb5
--- /dev/null
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const util = require('../../lib/otf-util');\r
+const request = require('request');\r
+module.exports = function (options = {}) { // eslint-disable-line no-unused-vars\r
+    return async context => {\r
+        let options = {\r
+            url: context.app.get('serviceApi').url + 'testStrategy/delete/v1/testDefinitionId/' + context.id,\r
+            headers: {\r
+                'Authorization': 'Basic ' + util.base64Encode(context.app.get('serviceApi').aafId + ':' + context.app.get('serviceApi').aafPassword)\r
+            },\r
+            rejectUnauthorized: false,\r
+        }\r
+        \r
+        await new Promise((resolve, reject) => {\r
+            request.delete(options, (err, res, body) => {\r
+                if(err){\r
+                    reject(err);\r
+                }\r
+                if(res && res.statusCode == 200){\r
+                    resolve(body);\r
+                }else{\r
+                    reject(res);\r
+                }\r
+            });\r
+        }).then(result => {\r
+            \r
+        }).catch(err => {\r
+            console.log(err);\r
+        });\r
+    };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/delete-version.js b/otf-frontend/server/src/feathers/hooks/delete-version.js
new file mode 100644 (file)
index 0000000..d3c3ab4
--- /dev/null
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const util = require('../../lib/otf-util');\r
+const request = require('request');\r
+const error = require('@feathersjs/errors');\r
+module.exports = function (options = {}) { // eslint-disable-line no-unused-vars\r
+    return async context => {\r
+\r
+        //Get test-definition to compare\r
+        let original = null;\r
+        await context.app.services[context.app.get('base-path') + 'test-definitions'].get(context.id, context.params).then(result => {\r
+            original = result;\r
+        });\r
+\r
+        //If there is a bpmn instance that is deployed and is no longer there, delete with service api\r
+        if (context.data.bpmnInstances && original) {\r
+            original.bpmnInstances.forEach(async (elem, val) => {\r
+                let found = false;\r
+                context.data.bpmnInstances.forEach((e, v) => {\r
+                    if (elem.version == e.version) {\r
+                        found = true;\r
+                    }\r
+                });\r
+                if (!found && elem.isDeployed) {\r
+                    let options = {\r
+                        url: context.app.get('serviceApi').url + 'testStrategy/delete/v1/deploymentId/' + elem.deploymentId,\r
+                        headers: {\r
+                            'Authorization': 'Basic ' + util.base64Encode(context.app.get('serviceApi').aafId + ':' + context.app.get('serviceApi').aafPassword)\r
+                        },\r
+                        rejectUnauthorized: false,\r
+                    }\r
+                    \r
+                    await new Promise((resolve, reject) => {\r
+                        request.delete(options, (err, res, body) => {\r
+                            if(err){\r
+                                reject(err);\r
+                            }\r
+                            if(res && res.statusCode == 200){\r
+                                resolve(res);\r
+                            }else{\r
+                                reject(res);\r
+                            }\r
+                        });\r
+                    }).then(result => {\r
+                        if(result.statusCode != 200){\r
+                            context.error = new error(result.statusCode);\r
+                            return Promise.reject(context.error);\r
+                        }\r
+                    }).catch(err => {\r
+                        \r
+                    });\r
+                }\r
+            });\r
+        }\r
+    };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/filters.js b/otf-frontend/server/src/feathers/hooks/filters.js
new file mode 100644 (file)
index 0000000..d81cd3f
--- /dev/null
@@ -0,0 +1,225 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Use this hook to manipulate incoming or outgoing data.\r
+// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html\r
+const { iff, disallow } = require('feathers-hooks-common');\r
+const errors = require('@feathersjs/errors');\r
+const { ObjectID } = require('mongodb');\r
+//const getEntity = async (context) => await new Promise((resolve, reject) => context.app.services[context.path].get(context.id || context.data._id, context.params).then(r => {resolve(r)}).catch(e => {reject(e)}));\r
+\r
+module.exports.groupFilter = function (options = null) {\r
+       return async context => {\r
+\r
+               \r
+               switch(context.method){\r
+                       case 'get':\r
+                               context.params.query._id = new ObjectID(context.id);\r
+\r
+                               let result = await context.app.services[context.app.get('base-path') + 'groups'].find(context.params);\r
+                               if(result.data && result.data.length > 0){\r
+                                       context.result = result.data[0];\r
+                               }else if(result.length > 0){\r
+                                       context.result = result[0];\r
+                               }else{\r
+                                       context.result = [];\r
+                               }\r
+                               break;\r
+                       case 'find':\r
+                               if(typeof context.params.user._id === 'string'){\r
+                                       context.params.user._id = new ObjectID(context.params.user._id);\r
+                               }\r
+\r
+                               if(!context.params.user.permissions.includes('admin')){\r
+                                       context.params.query['members.userId'] = context.params.user._id;\r
+                               }\r
+\r
+                               let lookup = context.params.query.lookup;\r
+                               delete context.params.query.lookup;\r
+\r
+                               // If graphLookup is supplied in the query params as true, lookup all parents and children\r
+                               if(lookup == 'up' || lookup == 'both'){\r
+                                       context.result = await new Promise((resolve, reject) => {\r
+                                               context.app.services[context.app.get('base-path') + 'groups'].Model.aggregate([\r
+                                                       {\r
+                                                               $match: context.params.query\r
+                                                       },\r
+                                                       {\r
+                                                               $graphLookup: {\r
+                                                                       from: "groups",\r
+                                                                       startWith: "$parentGroupId",\r
+                                                                       connectFromField: "parentGroupId",\r
+                                                                       connectToField: "_id",\r
+                                                                       as: "parentGroups"\r
+                                                               }\r
+                                                       }\r
+                                               ]).then(res => {\r
+                                                       resolve(res);\r
+                                               }).catch(err => {\r
+                                                       throw new errors.GeneralError(err);\r
+                                               })\r
+                                       });\r
+                               }\r
+                               \r
+                               //if user is an admin in one of ther groups, find children groups\r
+                               if(lookup == 'down' || lookup == 'both'){\r
+                                       //this will be set if lookup already occured\r
+                                       if(context.result){\r
+                                               for(let i = 0; i < context.result.length; i++){\r
+                                                       //only find children if they are admins\r
+                                                       if(checkGroupForPermission(context.result[i], context.params.user, 'management')){\r
+                                                               let children = await getChildGroups(context.app.services[context.app.get('base-path') + 'groups'].Model, context.result[i]);\r
+                                                               context.result[i]['childGroups'] = children;\r
+                                                       }\r
+                                               }\r
+                                       }else{\r
+                                               context.result = await new Promise(async (resolve, reject) => {\r
+                                                       context.app.services[context.app.get('base-path') + 'groups'].find(context.params).then(async res => {\r
+                                                               let results;\r
+                                                               if(res.total){\r
+                                                                       results = res.data;\r
+                                                               }else{\r
+                                                                       results = res;\r
+                                                               }\r
+                                                               for(let i = 0; i < results.length; i++){\r
+                                                                       if(checkGroupForPermission(results[i], context.params.user, 'management')){\r
+                                                                               results[i]['childGroups'] = await getChildGroups(context.app.services[context.app.get('base-path') + 'groups'].Model, results[i]);\r
+                                                                       }\r
+                                                               }\r
+                                                               resolve(results);\r
+                                                       }).catch(err => {\r
+                                                               throw new errors.GeneralError(err);\r
+                                                       })\r
+                                               });\r
+                                       }\r
+                               }\r
+\r
+                               break;\r
+\r
+                       case 'create':\r
+                               break;\r
+\r
+                       case 'update':\r
+                       case 'patch':\r
+                       case 'remove':\r
+                               break;\r
+\r
+                       default:\r
+                               break;\r
+\r
+\r
+               }\r
+\r
+               return context;\r
+       };\r
+};\r
+\r
+getChildGroups = async function(model, group){\r
+       return new Promise(async (resolve, reject) => {\r
+               let childGroups = [];\r
+               model.aggregate([\r
+                       {\r
+                               $match: {\r
+                                       'parentGroupId': group._id\r
+                               }\r
+                       }\r
+               ]).then(async res => {\r
+                       if(res.length > 0){\r
+                               for(let i = 0; i < res.length; i++){\r
+                                       childGroups.push(res[i]);\r
+                                       let childern = await getChildGroups(model, res[i]);\r
+                                       childern.forEach((elem, val) => {\r
+                                               childGroups.push(elem);\r
+                                       });\r
+                               }\r
+                       }\r
+                       resolve(childGroups);\r
+               }).catch(err => {\r
+                       reject(err);\r
+               })\r
+\r
+       })\r
+}\r
+\r
+checkGroupForPermission = function(group, user, permission){\r
+       let result = false;\r
+       group.members.forEach((member, val) => {\r
+               if(member.userId.toString() == user._id.toString()){\r
+                       group.roles.forEach((e,v) => {\r
+                               if(e.permissions.includes(permission)){\r
+                                       if(member.roles.includes(e.roleName)){\r
+                                               result = true;\r
+                                               return;\r
+                                       }\r
+                               }\r
+                       });\r
+                       return;\r
+               }\r
+       })\r
+       return result;\r
+}\r
+\r
+module.exports.afterGroups = function(){\r
+       return async context => {\r
+\r
+       }\r
+}\r
+\r
+module.exports.userFilter = function (){\r
+       return async context => {\r
+               \r
+               if(context.params.query){\r
+                       context.params.query._id = context.params.user._id;\r
+               }\r
+               if(context.id && context.id != context.params.user._id){\r
+                       throw new errors.Forbidden();\r
+               }\r
+               if(context.data){\r
+                       if(context.data._id && context.data._id != context.params.user._id){\r
+                               throw new errors.Forbidden();\r
+                       }\r
+                       //should not be able to edit their groups\r
+                       delete context.data.groups;\r
+                       //should not be able to edit their permissions\r
+                       delete context.data.permissions;\r
+                       \r
+                       delete context.data.createdAt;\r
+                       delete context.data.updatedAt;\r
+                       delete context.data.enabled;\r
+               }\r
+       }\r
+}\r
+\r
+module.exports.getGroupFilter = function (options = { key: 'groupId' }) {\r
+       return async hook => {\r
+               if(!hook.params.query){\r
+                       hook.params.query = {};\r
+               }\r
+               \r
+               hook.params.query._id = hook.id;\r
+               delete hook.id;\r
+               \r
+               return hook.service.find(hook.params)\r
+                       .then(result => {\r
+                               if (result.data) {\r
+                                       hook.result = result.data[0];\r
+                               } else {\r
+                                       hook.result = result;\r
+                               }\r
+                               return hook;\r
+                       });\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/log.js b/otf-frontend/server/src/feathers/hooks/log.js
new file mode 100644 (file)
index 0000000..253c113
--- /dev/null
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// A hook that logs service method before, after and error\r
+// See https://github.com/winstonjs/winston for documentation\r
+// about the logger.\r
+const logger = require('../../lib/logger');\r
+const util = require('util');\r
+\r
+// To see more detailed messages, uncomment the following line:\r
+// logger.level = 'debug';\r
+\r
+module.exports = function () {\r
+       return context => {\r
+               // This debugs the service call and a stringified version of the hook context\r
+               // You can customize the message (and logger) to your needs\r
+               logger.debug(`${context.type} app.service('${context.path}').${context.method}()`);\r
+\r
+               if (typeof context.toJSON === 'function' && logger.level === 'debug') {\r
+                       logger.debug('Hook Context', util.inspect(context, { colors: false }));\r
+               }\r
+\r
+               if (context.error) {\r
+                       logger.error(context.error.stack);\r
+               }\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/paginate-option.js b/otf-frontend/server/src/feathers/hooks/paginate-option.js
new file mode 100644 (file)
index 0000000..67eb1f0
--- /dev/null
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Gives client option to disable pagination by setting $limit to -1 in params\r
+module.exports = function () {\r
+       return async context => {\r
+               if (context.params.query && context.params.query.$limit == '-1') {\r
+                       context.params.paginate = false;\r
+                       delete context.params.query.$limit;\r
+               }\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/hooks/permissions/abilities.js b/otf-frontend/server/src/feathers/hooks/permissions/abilities.js
new file mode 100644 (file)
index 0000000..8d1d3e5
--- /dev/null
@@ -0,0 +1,110 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { AbilityBuilder, Ability } = require('@casl/ability');\r
+const config = require('../../../../config/default.json');\r
+\r
+Ability.addAlias('read', ['get', 'find']);\r
+Ability.addAlias('write', ['create', 'update', 'patch']);\r
+Ability.addAlias('delete', ['remove']);\r
+Ability.addAlias('execute', ['create', 'remove']);\r
+module.exports.defineAbilitiesFor = function (user, groups) {\r
+    const { rules, can, cannot } = AbilityBuilder.extract();\r
+    \r
+    // If user is a site wide admin, they get all access\r
+    if(user.permissions.includes('admin')){\r
+        can('execute', 'all');\r
+        can('management', 'all');\r
+        can('crud', 'all');\r
+        can('patch', 'all');\r
+        return new Ability(rules);\r
+    }\r
+\r
+    //Permissions associated to roles within groups\r
+       groups.forEach((elem, val) => {\r
+\r
+        if(elem.permissions.includes('management')){\r
+            can('management', 'groups', {_id: elem._id});\r
+            can('write', 'groups', ['groupDescription', 'members', 'mechanizedIds', 'roles', 'updatedAt', 'updatedBy'], { _id: elem._id });\r
+            can('write', 'groups', ['ownerId'], { _id: elem._id, ownerId: user._id});\r
+\r
+            //remove management from the array of permissions\r
+            elem.permissions.splice(elem.permissions.indexOf('management'), 1);\r
+        }\r
+\r
+        //Executing Test Instances\r
+        if(elem.permissions.includes('execute')){\r
+            can('execute', 'execute');\r
+            can('execute', 'testInstances', { groupId: elem._id });\r
+            can('create', 'jobs');\r
+            can('remove', 'jobs');\r
+            \r
+            //remove execute permission from the array of permissions\r
+            elem.permissions.splice(elem.permissions.indexOf('execute'), 1);\r
+        }\r
+\r
+        //Test Heads can be accessed by members of the group\r
+        can(elem.permissions, 'testHeads', { groupId: elem._id });\r
+\r
+               //Test Definitions can be accessed by members of the group\r
+        can(elem.permissions, 'testDefinitions', { groupId: elem._id });\r
+\r
+               //Test Instances can be accessed by members of the group\r
+        can(elem.permissions, 'testInstances', { groupId: elem._id });\r
+\r
+        //Test Executions can be accessed by members of the group\r
+        can('read', 'testExecutions', { groupId: elem._id });\r
+        can('read', 'testExecutions', ["_id", "groupId", "testHeadResults.testHeadId", "testHeadResults.testHeadName", "testHeadResults.testHeadGroupId", "testHeadResults.startTime", "testHeadResults.endTime"], {"testHeadResults.testHeadGroupId": elem._id});\r
+\r
+    });\r
+\r
+    /*************************************\r
+    *   TEST HEADS access\r
+    */\r
+\r
+    //-- READ\r
+    // Users can read all public test heads\r
+    can('read', 'testHeads', { isPublic: true });\r
+\r
+    // Users should never be able to read the credential \r
+    cannot('read', 'testHeads', ['authorizationCredential']);\r
+\r
+    //-- EXECUTE\r
+    // Users can execute all public test heads\r
+    can('execute', 'testHeads', { isPublic: true });\r
+\r
+    /*************************************\r
+    *   USERS access\r
+    */\r
+\r
+    //-- READ\r
+\r
+    // Users should be able to view all users' basic information, and can read more information if it is their user object\r
+    can('read', 'users', ['_id', 'firstName', 'lastName', 'email']);\r
+    can('read', 'users', ['permissions', 'favorites', 'defaultGroup', 'defaultGroupEnabled'], { _id: user._id });\r
+\r
+    //-- WRITE\r
+\r
+    // Users should be able to only edit specific fields from their user object\r
+    can('write', 'users', ['password', 'favorites', 'defaultGroup', 'defaultGroupEnabled', 'updatedBy', 'updatedAt'], { _id: user._id })\r
+\r
+    \r
+\r
+    //Authentication\r
+    can(['create', 'remove'], 'authentication');\r
+    \r
+    return new Ability(rules);\r
+}   
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/hooks/permissions/get-permissions.js b/otf-frontend/server/src/feathers/hooks/permissions/get-permissions.js
new file mode 100644 (file)
index 0000000..f6922e5
--- /dev/null
@@ -0,0 +1,93 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function(userObject, groups, defaultPermissions = []){\r
+    return getPermissions(userObject, groups, defaultPermissions);\r
+}\r
+\r
+function getPermissions(userObject, groups, defaultPermissions = []){\r
+    if(!groups || !groups.length){\r
+        return [];\r
+    }\r
+\r
+    let results = [];\r
+\r
+    for(let i = 0; i < groups.length; i++){\r
+\r
+        //Get user's roles in group\r
+        let userInfo = groups[i].members.find((e) => {\r
+            return e.userId.toString() == userObject._id.toString();\r
+        });\r
+\r
+        //grab permissions\r
+        //add default that was passed in\r
+        let perms = JSON.parse(JSON.stringify(defaultPermissions));\r
+\r
+        if(userInfo){\r
+            groups[i].roles.forEach((elem, val) => {\r
+                if(userInfo.roles.includes(elem.roleName)){\r
+                    elem.permissions.forEach(elem => {\r
+                        perms.push(elem);\r
+                    })\r
+                }\r
+            });\r
+        }\r
+\r
+        addGroupPermissions(results, groups[i], perms);\r
+\r
+        //Run recusivley for parent and child groups\r
+        if(groups[i].parentGroups){\r
+            groups[i].parentGroups.forEach((e, v) => {\r
+                addGroupPermissions(results, e, ['read'])\r
+            });\r
+        }\r
+        if(groups[i].childGroups){\r
+            groups[i].childGroups.forEach((e,v) => {\r
+                addGroupPermissions(results, e, perms);\r
+            });\r
+        }\r
+\r
+    }\r
+\r
+    return results;\r
+}\r
+\r
+function addGroupPermissions(results, group, permissions){\r
+\r
+    // Look for group in result to make sure it doesnt alreay exist\r
+    let groupIndex = null;\r
+    results.forEach((elem, val) => {\r
+        if(elem._id.toString() == group._id.toString()){\r
+            groupIndex = val;\r
+            return;\r
+        }\r
+    })\r
+\r
+    //If group doesn't exist add it to the array.\r
+    if(groupIndex == null){\r
+        groupIndex = results.push(group) - 1;\r
+    }\r
+\r
+    //add permissions to group \r
+    if(results[groupIndex].permissions){\r
+        permissions = permissions.concat(results[groupIndex].permissions);\r
+    }\r
+\r
+    permissions = new Set(permissions);\r
+\r
+    //set permissions\r
+    results[groupIndex].permissions = Array.from(permissions);\r
+}
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/hooks/permissions/permissions.js b/otf-frontend/server/src/feathers/hooks/permissions/permissions.js
new file mode 100644 (file)
index 0000000..3659bde
--- /dev/null
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { defineAbilitiesFor } = require('./abilities.js');\r
+const { toMongoQuery } = require('@casl/mongoose')\r
+const getPermissions = require('./get-permissions.js');\r
+const { Forbidden } = require('@feathersjs/errors');\r
+//const pick = require('lodash.pick');\r
+var dot = require('dot-object');\r
+const pick = require('object.pick');\r
+const { ObjectID } = require('mongodb');\r
+\r
+permissions = function (name = null) {\r
+       return async context => {\r
+\r
+               if (!context.params.provider) {\r
+                       return Promise.resolve(context);\r
+               }\r
+\r
+               const action = context.method;\r
+               const service = name ? context.app.service(context.path) : context.service;\r
+               const serviceName = name || context.path;\r
+\r
+               let groupQueryParams = JSON.parse(JSON.stringify(context.params));\r
+               groupQueryParams.query = {\r
+                       lookup: 'both'\r
+               }\r
+\r
+               //get groups list\r
+               let groups = await context.app.services[context.app.get('base-path') + 'groups'].find(groupQueryParams);\r
+\r
+               //organize permissions for the groups\r
+               let groupsPermissions = getPermissions(context.params.user, groups);\r
+\r
+               //Define Abilities for the user\r
+               const ability = defineAbilitiesFor(context.params.user, groupsPermissions);\r
+\r
+               //Define the fields that they have access to\r
+               let allowedFields;\r
+               if(service.Model){\r
+                       allowedFields = service.Model.accessibleFieldsBy(ability, context.method);\r
+               }\r
+\r
+               const throwUnlessCan = (action, resource, field = null) => {\r
+                       let instance = resource;\r
+                       if(service.Model && typeof resource === 'object'){\r
+                               instance = new service.Model(resource);\r
+                       }else{\r
+                               instance = serviceName;\r
+                       }\r
+       \r
+                       if (ability.cannot(action, instance, field)) {\r
+                               let message = `You are not allowed to ${action} ${serviceName}`;\r
+\r
+                               if(field){\r
+                                       message += ` on field ${field}`;\r
+                               }\r
+\r
+                               throw new Forbidden(message);\r
+                       }\r
+               }\r
+\r
+               context.params.ability = ability;\r
+\r
+\r
+               if (context.method === 'create') {\r
+                       throwUnlessCan('create', context.data);\r
+               }\r
+\r
+               if (!context.id) {               \r
+\r
+                       throwUnlessCan(context.method, serviceName);\r
+                       \r
+                       const query = toMongoQuery(ability, serviceName, action);\r
+                       \r
+                       if (query !== null) {\r
+                               if(context.params.query.$or && query.$or){\r
+                                       query.$and = [\r
+                                               {$or: Object.assign([], context.params.query.$or)},\r
+                                               {$or: Object.assign([], query.$or)}\r
+                                       ];\r
+                                       delete context.params.query.$or;\r
+                                       delete query.$or;\r
+                               }\r
+\r
+                               Object.assign(context.params.query, query);\r
+\r
+                       } else {\r
+                               context.params.query.$limit = 0;\r
+                       }\r
+\r
+                       if(context.params.query.$select){\r
+                               //context.params.query.$select = context.params.query.$select.filter(elem => allowedFields.includes(elem));\r
+                               context.params.query.$select = context.params.query.$select.filter(elem => {\r
+                                       for(let i = 0; i < allowedFields.length; i++){\r
+                                               \r
+                                               //if there is dot notation, then it only looks at the parent variable name\r
+                                               elem = elem.toString().match(new RegExp(/^(\w+)/))[0];\r
+\r
+                                               if(allowedFields[i] == elem){\r
+                                                       return true;\r
+                                               }\r
+\r
+                                       };\r
+\r
+                                       return false;\r
+                               });\r
+                               \r
+\r
+\r
+                       }else{\r
+                               context.params.query.$select = allowedFields;\r
+                       }\r
+\r
+                       if(context.params.query.$select && context.params.query.$select.length == 0){\r
+                               context.params.query.$select = allowedFields;\r
+                       }\r
+\r
+                       if(!context.params.query.$select){\r
+                               context.params.query.$select = [];\r
+                       }\r
+                       //groupId is used for permissions conditions and must be selected\r
+                       if(!context.params.query.$select.includes('groupId')){\r
+                               context.params.query.$select.push('groupId');\r
+                       }\r
+                       \r
+                       return context;\r
+               }\r
+\r
+               const params = Object.assign({}, context.params, { provider: null });\r
+\r
+               const result = await service.get(context.id, params);\r
+               throwUnlessCan(action, result);\r
+\r
+               if (action === 'get') {\r
+                       context.result = pick(result, allowedFields);\r
+               }else{\r
+                       if(context.data){\r
+                               Object.keys(context.data).forEach(key => {\r
+                                       if(key == "$push"){\r
+                                               Object.keys(context.data['$push']).forEach(k => {\r
+                                                       throwUnlessCan(action, result, k);\r
+                                               });\r
+                                       }else{\r
+                                               throwUnlessCan(action, result, key);\r
+                                       }\r
+                               })\r
+                       }\r
+                       //context.data = pick(context.data, allowedFields);\r
+               }\r
+\r
+               return context;\r
+\r
+       }\r
+}\r
+\r
+makeObjectIdString = function(obj) {\r
+       for (var property in obj) {\r
+               if (obj.hasOwnProperty(property)) {\r
+                       if (typeof obj[property] == "object"){\r
+                               if(ObjectID.isValid(obj[property])) {\r
+                                       obj[property] = obj[property].toString()\r
+                               }else{\r
+                                       makeObjectIdString(obj[property]);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+myPick = function(elem, allowedFields){\r
+       //when turning the object into dot notation, we loose the\r
+       makeObjectIdString(elem);\r
+\r
+       let d = dot.dot(elem);\r
+       let toPick = [];\r
+       Object.keys(d).forEach((key) => {\r
+               allowedFields.forEach((f, i) => {\r
+                       let r = '^' + f;\r
+                       if(key.replace(/\.([0-9]+)\./g, '.').match(new RegExp(r))){\r
+                               toPick.push(key);\r
+                       }\r
+               })\r
+       });\r
+       let picked = pick(d, toPick);\r
+       let obj = dot.object(picked)\r
+       return obj;\r
+}\r
+\r
+limitFields = function(){\r
+       return async context => {\r
+               if(context.result.data && context.result.data.length != undefined){\r
+                       //checkFields(context.params.ability, context.result.data, context.service.Model);\r
+                       context.result.data.forEach((elem, val) => {\r
+                               let instance = new context.service.Model(elem);\r
+                               const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
+                               //context.result.data[val] = pick(elem, allowedFields);\r
+                               context.result.data[val] = myPick(elem, allowedFields);\r
+                       });\r
+               }else if(context.result && context.result.length != undefined){\r
+                       context.result.forEach((elem, val) => {\r
+                               let instance = new context.service.Model(elem);\r
+                               const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
+                               //context.result[val] = pick(elem, allowedFields);\r
+                               context.result[val] = myPick(elem, allowedFields);\r
+                       });\r
+               }else if(context.result){\r
+                       //checkFields(context.params.ability, context.result, context.service.Model); \r
+                       let instance = new context.service.Model(context.result);\r
+                       let allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
+                       //context.result = pick(context.result, allowedFields);\r
+                       context.result = myPick(context.result, allowedFields);\r
+               }\r
+       }\r
+}\r
+\r
+\r
+module.exports = {\r
+       permissions: permissions,\r
+       limitFields: limitFields\r
+}\r
diff --git a/otf-frontend/server/src/feathers/hooks/testDefinitionIsDeployed.js b/otf-frontend/server/src/feathers/hooks/testDefinitionIsDeployed.js
new file mode 100644 (file)
index 0000000..1ca50a1
--- /dev/null
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function (context) {\r
+       return async context => {\r
+        function hasDeployedBpmn(){\r
+            return context.app.services[context.app.get('base-path') + 'test-definitions']\r
+                    .get(context.data.testDefinitionId, context.params)\r
+                    .then(result => {\r
+                        \r
+                        if(!result.bpmnInstances){\r
+                            return false;\r
+                        }\r
+                       \r
+                        for(let i = 0; i < result.bpmnInstances.length; i++){\r
+                            if(result.bpmnInstances[i].isDeployed){\r
+                                return true;\r
+                            }\r
+                        }\r
+                        return false;\r
+                    })\r
+                    .catch(err => {\r
+                        console.log(err);\r
+                    });\r
+           \r
+        }\r
+        \r
+        if(context.data.processDefinitionId === '' && !context.data.useLatestTestDefinition){\r
+            return false;\r
+        }\r
+        if(!hasDeployedBpmn()){\r
+            return false;\r
+        }\r
+        if(hasDeployedBpmn() && (context.data.useLatestTestDefinition || context.data.processDefinitionId !== '') ){\r
+            return true;\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/hooks/throw.js b/otf-frontend/server/src/feathers/hooks/throw.js
new file mode 100644 (file)
index 0000000..9e55af0
--- /dev/null
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function(error = 'Error') {\r
+    return async context => {\r
+        throw error;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/hooks/updatedBy.js b/otf-frontend/server/src/feathers/hooks/updatedBy.js
new file mode 100644 (file)
index 0000000..cd1cdae
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function () {\r
+       return async context => {\r
+        if(!context.data){\r
+            context.data = {};\r
+        }\r
+        if(context.params.user){\r
+            context.data.updatedBy = context.params.user._id;\r
+        }\r
+               return context;\r
+       };\r
+};\r
diff --git a/otf-frontend/server/src/feathers/index.js b/otf-frontend/server/src/feathers/index.js
new file mode 100644 (file)
index 0000000..ad37c1b
--- /dev/null
@@ -0,0 +1,166 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Node.js modules\r
+const path = require('path');\r
+const https = require('https');\r
+const http = require('http');\r
+const fs = require('fs');\r
+\r
+// Express.js modules\r
+const express = require('@feathersjs/express');\r
+const compress = require('compression');\r
+const helmet = require('helmet');\r
+const cors = require('cors');\r
+const favicon = require('serve-favicon');\r
+\r
+// Feathers.js modules\r
+const feathers = require('@feathersjs/feathers');\r
+const configuration = require('@feathersjs/configuration');\r
+const socketio = require('@feathersjs/socketio'); //require('@feathersjs/socketio-client'); \r
+const io = require('socket.io'); //socket.io-client\r
+const socket = io();\r
+\r
+const services = require('./services');\r
+const appHooks = require('./app.hooks');\r
+const channels = require('./channels');\r
+const authentication = require('./authentication');\r
+\r
+// Mongoose\r
+const mongoose = require('../lib/mongoose');\r
+const _mongoose = require('mongoose');\r
+\r
+// Mongoose Plugins\r
+const { accessibleRecordsPlugin, accessibleFieldsPlugin } = require('@casl/mongoose');\r
+_mongoose.plugin(accessibleFieldsPlugin);\r
+_mongoose.plugin(accessibleRecordsPlugin);\r
+\r
+// Winston\r
+const logger = require('../lib/logger');\r
+\r
+// Redis\r
+const redis = require('redis');\r
+\r
+// Create a Express/Feathers application\r
+const app = express(feathers());\r
+\r
+// Load app configuration\r
+app.configure(configuration());\r
+\r
+// Enable security, CORS, compression, favicon and body parsing\r
+app.use(helmet());\r
+app.use(cors());\r
+app.use(compress());\r
+app.use(express.json());\r
+app.use(express.urlencoded({ extended: true }));\r
+\r
+// Set up Plugins and providers\r
+app.configure(express.rest());\r
+app.configure(socketio(function (io) {\r
+       io.on('connection', (socket) => {\r
+               console.log('someone has connected')\r
+               io.emit('message', "HI from nodejs");\r
+       });\r
+       // Registering Socket.io middleware\r
+       io.use(function (socket, next) {\r
+               // Exposing a request property to services and hooks\r
+               socket.feathers.referrer = socket.request.referrer;\r
+               next();\r
+       });\r
+}))\r
+//app.configure(socketio());\r
+\r
+// const subscribe = redis.createClient(6379, 'localhost');\r
+// subscribe.subscribe('otf.execution.queue');\r
+\r
+// subscribe.on('connect', function () {\r
+//     console.log("Connected to reids server")\r
+// })\r
+\r
+// subscribe.on('message', function (channel, message) {\r
+//     console.log('Channel: ' + channel + ', Message: ' + message);\r
+//     //client.sent(message);\r
+// });\r
+\r
+// io.on('connection', (socket) => {\r
+//     console.log('user connected');\r
+\r
+//     socket.on('message', (message) => {\r
+//             console.log("Message Received: " + message);\r
+//             io.emit('message', {type: 'new-message', text: message})\r
+//     });\r
+// });\r
+\r
+// Configure Mongoose driver before setting up services that use Mongoose\r
+app.configure(mongoose);\r
+\r
+// Set up database dependent components once the connection is ready to prevent unexpected results\r
+_mongoose.connection.on('open', (ref) => {\r
+       app.configure(authentication);\r
+\r
+       // Set up our services (see `services/index.js`)\r
+       app.configure(services);\r
+       // Set up event channels (see channels.js)\r
+       app.configure(channels);\r
+\r
+       const userInterfacePath = path.join(__dirname, '..', '..', '..', 'client', 'dist');\r
+\r
+       app.use('/', express.static(userInterfacePath));\r
+\r
+       app.all('/*', function (req, res) {\r
+               res.sendFile(path.join(userInterfacePath, 'index.html'), function (err) {\r
+                       if (err) {\r
+                               res.status(500).send('Internal Server Error - This incident has been reported.');\r
+                               logger.error(JSON.stringify(err));\r
+                       }\r
+               });\r
+       });\r
+\r
+       // Configure a middleware for 404s and the error handler\r
+       app.use(express.notFound());\r
+       app.use(express.errorHandler({ logger }));\r
+\r
+       app.hooks(appHooks);\r
+\r
+       const port = app.get('port');\r
+       const useSSL = app.get('ssl');\r
+       var server = null;\r
+\r
+       if(useSSL){\r
+               // set up server with ssl (https)\r
+               const certDirPath = path.join(__dirname, '..', '..', '..', 'server', 'config', 'cert');\r
+\r
+               server = https.createServer({\r
+                       key: fs.readFileSync(path.normalize(certDirPath + path.sep + 'privateKey.pem')),\r
+                       cert: fs.readFileSync(path.normalize(certDirPath + path.sep + 'otf.pem'))\r
+               }, app).listen(port);\r
+       }else{\r
+               // set up server without ssl (http)\r
+               server = http.createServer(app).listen(port);\r
+       }\r
+\r
+       app.setup(server);\r
+\r
+       process.on('unhandledRejection', (reason, p) =>\r
+               logger.error('Unhandled Rejection at: Promise ', p, reason)\r
+       );\r
+\r
+       server.on('listening', () =>\r
+               logger.info('Feathers application started on http://%s:%d', app.get('host'), port)\r
+       );\r
+});\r
+\r
+module.exports = app;\r
diff --git a/otf-frontend/server/src/feathers/models/file.model.js b/otf-frontend/server/src/feathers/models/file.model.js
new file mode 100644 (file)
index 0000000..a41d24c
--- /dev/null
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const mongooseGridFS = require('mongoose-gridfs');\r
+\r
+module.exports = function (app) {\r
+       const mongoose = app.get('mongooseClient');\r
+\r
+       const gridfs = mongooseGridFS({\r
+               collection: 'fs',\r
+               model: 'File',\r
+               mongooseConnection: mongoose.connection\r
+       });\r
+\r
+       return gridfs.model;\r
+};\r
diff --git a/otf-frontend/server/src/feathers/models/groups.model.js b/otf-frontend/server/src/feathers/models/groups.model.js
new file mode 100644 (file)
index 0000000..d15894e
--- /dev/null
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+       const { Schema } = mongooseClient;\r
+       const groups = new Schema({\r
+               groupName: { type: String, required: true },\r
+               groupDescription: { type: String },\r
+               parentGroupId: { type: Schema.Types.ObjectId, ref: 'groups' },\r
+               members: [ new Schema({\r
+                       userId: { type: Schema.Types.ObjectId, ref: 'users' },\r
+                       roles: { type: Array, default: ['user'] }\r
+               },  { _id: false })],\r
+               roles: [new Schema({\r
+                       roleName: { type: String },\r
+                       permissions: { type: Array, default: ['read'] }\r
+               }, {_id: false})],\r
+               ownerId: { type: Schema.Types.ObjectId, ref: 'users', required: true },\r
+               mechanizedIds: [String]\r
+       }, {\r
+               timestamps: true\r
+       });\r
+\r
+       return mongooseClient.model('groups', groups);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/models/jobs.model.js b/otf-frontend/server/src/feathers/models/jobs.model.js
new file mode 100644 (file)
index 0000000..35ad8b3
--- /dev/null
@@ -0,0 +1,45 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+       const { Schema } = mongooseClient;\r
+\r
+       const jobs = new Schema({\r
+               name: { type: String },\r
+               data: { type: new Schema({\r
+                       testSchedule: {\r
+                               testInstanceId: { type: Schema.Types.ObjectId },\r
+                               testInstanceStartDate: { type: String },\r
+                               async: { type: Boolean },\r
+                               asyncTopic: { type: String },\r
+                               testInstanceExecFreqInSeconds: { type: Number },\r
+                               testInstanceEndDate: { type: String },\r
+                               executorId: { type: Schema.Types.ObjectId }\r
+                       },\r
+                       authorizationHeader: { type: String }\r
+               }) },\r
+               type: { type: String },\r
+               nextRunAt: { type: String },\r
+               lastModifiedBy: { type: String },\r
+               lockedAt: { type: String },\r
+               lastRunAt: { type: String }\r
+       }, {\r
+               timestamps: true\r
+       });\r
+\r
+       return mongooseClient.model('jobs', jobs, 'agenda');\r
+};\r
diff --git a/otf-frontend/server/src/feathers/models/test-definitions.model.js b/otf-frontend/server/src/feathers/models/test-definitions.model.js
new file mode 100644 (file)
index 0000000..9e71a0f
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+       const { Schema } = mongooseClient;\r
+       const testDefinitions = new Schema({\r
+               testName: { type: String, required: true },\r
+               testDescription: { type: String, required: true },\r
+               processDefinitionKey: { type: String, unique: true },\r
+               creatorId: { type: Schema.Types.ObjectId, ref: 'users', required: true },\r
+               groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },\r
+               bpmnInstances: [new Schema({\r
+                       processDefinitionId: { type: String },\r
+                       deploymentId: { type: String },\r
+                       version: { type: String, required: true },\r
+                       bpmnFileId: { type: Schema.Types.ObjectId, ref: 'files', required: true },\r
+                       resourceFileId: {type: Schema.Types.ObjectId, ref: 'files' },\r
+                       isDeployed: { type: Boolean, required: true, default: false },\r
+                       testHeads: [new Schema({\r
+                               testHeadId: { type: Schema.Types.ObjectId, ref: 'testHeads' },\r
+                               bpmnVthTaskId: { type: String },\r
+                               label: { type: String, default: '' }\r
+                       }, { _id: false })],\r
+                       pflos: [new Schema({\r
+                               bpmnPfloTaskId: { type: String },\r
+                               label: { type: String, default: '' }\r
+                       }, { _id: false })],\r
+                       testDataTemplate: { type: Object, default: {} }, \r
+                       updatedBy: { type: Schema.Types.ObjectId, ref: 'users'},\r
+                       createdBy: { type: Schema.Types.ObjectId, ref: 'users'}\r
+               }, { _id: false, timestamps: true })],\r
+               disabled: {type: Boolean, default: false},\r
+               updatedBy: { type: Schema.Types.ObjectId, ref: 'users'},\r
+               createdBy: { type: Schema.Types.ObjectId, ref: 'users'}\r
+       }, {\r
+       timestamps: true,\r
+       minimize: false\r
+       });\r
+\r
+       \r
+       \r
+       return mongooseClient.model('testDefinitions', testDefinitions, 'testDefinitions');\r
+};\r
diff --git a/otf-frontend/server/src/feathers/models/test-executions.model.js b/otf-frontend/server/src/feathers/models/test-executions.model.js
new file mode 100644 (file)
index 0000000..2f152ac
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+\r
+       const { Schema } = mongooseClient;\r
+       const testExecutions = new Schema({\r
+               processInstanceId: { type: String, required: true },\r
+               businessKey: { type: String },\r
+               testResult: { type: String },\r
+               testDetails: { type: Object },\r
+               startTime: { type: Date },\r
+               endTime: { type: Date },\r
+               async: { type: Boolean },\r
+               asyncTopic: { type: String },\r
+               groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },\r
+               executorId: { type: Schema.Types.ObjectId, ref: 'users', required: true },\r
+               testResultMessage: { type: String },\r
+               testHeadResults: [\r
+                       new Schema({\r
+                               testHeadName: { type: String },\r
+                               testHeadId: { type: Schema.Types.ObjectId, ref: 'testHeads' },\r
+                               testHeadGroupId: { type: Schema.Types.ObjectId, ref: 'groups' }\r
+                       }, {_id: false})\r
+               ],\r
+               testInstanceResults: [{}],\r
+               historicEmail: { type: String },\r
+               historicTestInstance: { type: Object },\r
+               historicTestDefinition: { type: Object }\r
+\r
+       }, {\r
+                       timestamps: false\r
+               });\r
+       \r
+       testExecutions.index({startTime: 1, endTime: 1});\r
+\r
+       return mongooseClient.model('testExecutions', testExecutions, 'testExecutions');\r
+};\r
diff --git a/otf-frontend/server/src/feathers/models/test-heads.model.js b/otf-frontend/server/src/feathers/models/test-heads.model.js
new file mode 100644 (file)
index 0000000..b1bb52d
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+       const { Schema } = mongooseClient;\r
+       const testHeads = new Schema({\r
+               testHeadName: { type: String, required: true, unique: true },\r
+               testHeadDescription: { type: String, required: true },\r
+               testHeadType: { type: String },\r
+               vthInputTemplate: { type: Object, default: {} },\r
+               //vthOutputTemplate: { type: Object, default: {} },\r
+               vendor: String,\r
+               port: { type: String },\r
+               hostname: { type: String },\r
+               resourcePath: { type: String },\r
+               creatorId: { type: Schema.Types.ObjectId, ref: 'users' },\r
+               groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },\r
+               authorizationType: { type: String },\r
+               authorizationCredential: { type: String },\r
+               authorizationEnabled: { type: Boolean, default: false },\r
+               isPublic: { type: Boolean }\r
+       }, {\r
+               timestamps: true\r
+       });\r
+\r
+       return mongooseClient.model('testHeads', testHeads, 'testHeads');\r
+};\r
diff --git a/otf-frontend/server/src/feathers/models/test-instances.model.js b/otf-frontend/server/src/feathers/models/test-instances.model.js
new file mode 100644 (file)
index 0000000..1cb5f0e
--- /dev/null
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+       const { Schema } = mongooseClient;\r
+       const uniqueTIByTD = new Schema({\r
+               testInstanceName: { type: String, required: true },\r
+               testDefinitionId: { type: Schema.Types.ObjectId, ref: 'testDefinitions', required: true }\r
+       });\r
+       uniqueTIByTD.index({\r
+               testInstanceName: 1,\r
+               testDefinitionId: 1,\r
+       }, {\r
+                       unique: true,\r
+               });\r
+\r
+       const testInstances = new Schema({\r
+\r
+               testInstanceDescription: { type: String },\r
+               testInstanceName: { type: String, required: true },\r
+               testDefinitionId: { type: Schema.Types.ObjectId, ref: 'testDefinitions', required: true },\r
+               useLatestTestDefinition: { type: Boolean, required: true },\r
+               processDefinitionId: { type: String, default: '' },\r
+               testData: { type: Object, default: {} },\r
+               internalTestData: { type: Object, default: {} },\r
+               simulationMode: { type: Boolean, default: false },\r
+               simulationVthInput: { type: Object, default: {} },\r
+               vthInput: { type: Object, default: {} },\r
+               pfloInput: { type: Object, default: {} },\r
+               disabled: { type: Boolean, default: false },\r
+               maxExecutionTimeInMillis: { type: Number, default: 0 },\r
+\r
+               groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },\r
+               updatedBy: { type: Schema.Types.ObjectId, ref: 'users' },\r
+               createdBy: { type: Schema.Types.ObjectId, ref: 'users' }\r
+       }, {\r
+                       timestamps: true,\r
+                       minimize: false\r
+               });\r
+       testInstances.index({\r
+               testInstanceName: 1,\r
+               testDefinitionId: 1,\r
+       }, {\r
+                       unique: true,\r
+               });\r
+\r
+       testInstances.post('save', function (error, doc, next) {\r
+               if (error.name === 'MongoError' && error.code === 11000) {\r
+                       next(new Error('Test Instance name must be unique per Test Definition'));\r
+               } else {\r
+                       next();\r
+               }\r
+       });\r
+\r
+       return mongooseClient.model('testInstances', testInstances, 'testInstances');\r
+};
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/models/users.model.js b/otf-frontend/server/src/feathers/models/users.model.js
new file mode 100644 (file)
index 0000000..aba50c1
--- /dev/null
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+module.exports = function (app) {\r
+       const mongooseClient = app.get('mongooseClient');\r
+       \r
+       const { Schema } = mongooseClient;\r
+       const users = new Schema({\r
+               firstName: { type: String, required: true },\r
+               lastName: { type: String, required: true },\r
+               email: { type: String, required: true, unique: true },\r
+               permissions: { type: Array, default: ['user'] },\r
+               enabled: { type: Boolean, required: true, default: false },\r
+               isVerified: { type: Boolean },\r
+               verifyToken: { type: String },\r
+               verifyExpires: { type: Date },\r
+               verifyChanges: { type: Object },\r
+               resetToken: { type: String },\r
+               resetExpires: { type: Date },\r
+               defaultGroup: { type: Schema.Types.ObjectId, ref: 'groups' },\r
+               defaultGroupEnabled: { type: Boolean, default: false },\r
+               password: { type: String, required: true },\r
+               favorites: new Schema({\r
+                       testDefinitions: [{type: Schema.Types.ObjectId, ref: 'testDefinitions'}]\r
+               }, { _id: false})\r
+       }, {\r
+               timestamps: true\r
+       });\r
+\r
+       return mongooseClient.model('users', users);\r
+\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/auth-management/auth-management.hooks.js b/otf-frontend/server/src/feathers/services/auth-management/auth-management.hooks.js
new file mode 100644 (file)
index 0000000..6cfb072
--- /dev/null
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const commonHooks = require('feathers-hooks-common');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [\r
+                       commonHooks.iff(\r
+                       commonHooks.isProvider('external'),\r
+                       commonHooks.preventChanges(\r
+                               'email',\r
+                               'isVerified',\r
+                               'verifyToken',\r
+                               'verifyShortToken',\r
+                               'verifyExpires',\r
+                               'verifyChanges',\r
+                               'resetToken',\r
+                               'resetShortToken',\r
+                               'resetExpires'\r
+                       ))],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [\r
+                       function(context){\r
+                               if(context.result['isVerified']){\r
+                                       context.result = {};\r
+                                       return context;\r
+                               }\r
+                               return context;\r
+                       }\r
+               ],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/auth-management/auth-management.service.js b/otf-frontend/server/src/feathers/services/auth-management/auth-management.service.js
new file mode 100644 (file)
index 0000000..cb3bafd
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `authmanagement` service on path `/authmanagement`\r
+// this service is used for user verification and management\r
+const authManagement = require('feathers-authentication-management');\r
+const hooks = require('./auth-management.hooks.js');\r
+const notifier = require('./notifier.js');\r
+\r
+module.exports = function (app) {\r
+\r
+       // Initialize our service with any options it requires\r
+       app.configure(authManagement({\r
+               path: app.get('base-path') + 'authManagement',\r
+               notifier: notifier(app).notifier,\r
+               service: app.get('base-path') + 'users'\r
+       }));\r
+\r
+       // Get our initialized service so that we can register hooks and filters\r
+       const service = app.service(app.get('base-path') + 'authManagement');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/auth-management/notifier.js b/otf-frontend/server/src/feathers/services/auth-management/notifier.js
new file mode 100644 (file)
index 0000000..75bbfa6
--- /dev/null
@@ -0,0 +1,120 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+var env = require('config').env;\r
+\r
+module.exports = function(app) {\r
+\r
+       function getLink(type, hash) {\r
+               const url = app.get('otf').url + "account/" + type + '?token=' + hash;\r
+               return url\r
+       }\r
+\r
+       function sendEmail(email) {\r
+               var environment = env.toUpperCase();\r
+               email.subject = "Open Test Framework (" + environment + ") - " + email.subject;\r
+               return app.service(app.get('base-path') + 'mailer').create(email).then(function (result) {\r
+                       console.log('Sent email', result)\r
+               }).catch(err => {\r
+                       console.log('Error sending email: ', email, err)\r
+               })\r
+       }\r
+\r
+       return {\r
+               notifier: function(type, user, notifierOptions) {\r
+                       let tokenLink;\r
+                       let email;\r
+                       let sender = app.get('otf').email;\r
+                       switch (type) {\r
+                               case 'resendVerifySignup': //sending the user the verification email\r
+                                       tokenLink = getLink('verify', user.verifyToken)\r
+                                       email = {\r
+                                               from: sender,\r
+                                               to: user['email'],\r
+                                               subject: 'Verify Signup',\r
+                                               html: 'Please verify your email address by clicking the link below.' + '</br>' + tokenLink\r
+\r
+                                       }\r
+                                       return sendEmail(email)\r
+                                       break\r
+\r
+                               case 'verifySignup': // confirming verification\r
+                                       let adminLink = app.get('otf').url + 'user-management?filter=' + user['email'];\r
+\r
+                                       email = {\r
+                                               from: sender,\r
+                                               to: user['email'],\r
+                                               subject: 'Signup Confirmed',\r
+                                               html: 'Thanks for verifying your email!' + '</br>' + 'You will be notified when an admin enables your account.'\r
+                                       }\r
+\r
+                                       let adminEmail = {\r
+                                               from: sender,\r
+                                               to: sender,\r
+                                               subject: 'Approve Verified User',\r
+                                               html:   'User has verified their email.' + '</br>' +\r
+                                                               'Details: ' + '</br>' +\r
+                                                               '   Email: ' + user['email'] + '</br>' +\r
+                                                               '   First Name: ' + user['firstName'] + '</br>' +\r
+                                                               '   Last Name: ' + user['lastName'] + '</br>' +\r
+                                                               '</br>' +\r
+                                                               'Enable their account by visiting ' + '</br>' + adminLink\r
+                                       }\r
+                                       sendEmail(adminEmail);\r
+                                       return sendEmail(email);\r
+                                       break\r
+\r
+                               case 'sendApprovalNotification':\r
+                                       email = {\r
+                                               from: sender,\r
+                                               to: user['email'],\r
+                                               subject: 'Approved',\r
+                                               html:   'Your account has been approved for access.' + '</br>' +\r
+                                                               'You can now log into the OTF website: ' + app.get('otf').url\r
+\r
+                                       }\r
+                                       return sendEmail(email);\r
+                                       break\r
+\r
+                               case 'sendResetPwd':\r
+                                       tokenLink = getLink('reset', user.resetToken)\r
+                                       email = {}\r
+                                       return sendEmail(email)\r
+                                       break\r
+\r
+                               case 'resetPwd':\r
+                                       tokenLink = getLink('reset', user.resetToken)\r
+                                       email = {}\r
+                                       return sendEmail(email)\r
+                                       break\r
+\r
+                               case 'passwordChange':\r
+                                       email = {}\r
+                                       return sendEmail(email)\r
+                                       break\r
+\r
+                               case 'identityChange':\r
+                                       tokenLink = getLink('verifyChanges', user.verifyToken)\r
+                                       email = {}\r
+                                       return sendEmail(email)\r
+                                       break\r
+\r
+                               default:\r
+                                       break\r
+                       }\r
+               }\r
+       }\r
+}\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.class.js b/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.class.js
new file mode 100644 (file)
index 0000000..fcc11e8
--- /dev/null
@@ -0,0 +1,196 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const axios = require('axios');\r
+const pickleRick = require('pickle-rick');\r
+const Response = require('http-response-object');\r
+const logger = require('../../../lib/logger');\r
+const util = require('../../../lib/otf-util');\r
+const Readable = require('stream').Readable;\r
+const request = require('request');\r
+\r
+class Service {\r
+       constructor(options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       async find(params) {\r
+               return [];\r
+       }\r
+\r
+       async get(id, params) {\r
+               return {\r
+                       id, text: `A new message with ID: ${id}!`\r
+               };\r
+       }\r
+\r
+       async create(data, params) {\r
+               if (Array.isArray(data)) {\r
+                       return Promise.all(data.map(current => this.create(current, params)));\r
+               }\r
+\r
+               data.deploying = true;\r
+\r
+               //let formData = new FormData();\r
+               let formData = {};\r
+               //prepare multipart form data\r
+               //formData.append('testDefinitionDeployerId', JSON.stringify(params.user._id));\r
+               formData['testDefinitionDeployerId'] = JSON.stringify(params.user._id);\r
+               if (data.testDefinition._id) {\r
+                       //formData.append('testDefinitionId', JSON.stringify(data.testDefinition._id));\r
+                       formData['testDefinitionId'] = JSON.stringify(data.testDefinition._id);\r
+               }\r
+\r
+               //If version was supplied change current version\r
+               if(data.version != null && data.version != undefined){\r
+                       data.testDefinition.currentVersion = data.testDefinition.bpmnInstances.findIndex(e => e.version == data.version);\r
+               }\r
+\r
+               //get bpmnfile\r
+               await this.options.app.services[this.options.app.get('base-path') + 'file-transfer'].get(data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].bpmnFileId)\r
+                       .then(result => {\r
+                               // let b = new Buffer(result);\r
+                               // console.log(b.toString())\r
+                               let s = new Readable();\r
+\r
+                               s.push(result);\r
+                               s.push(null);\r
+                               formData['bpmn'] = s.read();\r
+                               // s.pipe(formData['bpmn']);\r
+                               \r
+                       }).catch(err => {\r
+                               console.log(err);\r
+                       });\r
+\r
+\r
+               //get resource zip file\r
+               if (data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].resourceFileId) {\r
+                       await this.options.app.services[this.options.app.get('base-path') + 'file-transfer'].get(data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].resourceFileId)\r
+                               .then(result => {\r
+                                       //let b = new Buffer(result);\r
+                                       let s = new Readable();\r
+\r
+                                       s.push(result);\r
+                                       s.push(null);\r
+                                       \r
+                                       formData['resources'] = s.read();\r
+                                       //formData.append('resource', s);\r
+\r
+                               }).catch(err => {\r
+                                       console.log(err);\r
+                               });\r
+               }\r
+\r
+               //prepare request\r
+               let options = {\r
+                       url: this.options.app.get('serviceApi').url + 'testStrategy/deploy/v1',\r
+                       headers: {\r
+                               'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),\r
+                               'Content-Type': "multipart/form-data"\r
+                       },\r
+                       rejectUnauthorized: false,\r
+                       formData: formData\r
+               }\r
+               let deployed = false;\r
+               let deployedDefinition;\r
+               let response;\r
+               await new Promise((resolve, reject) => {\r
+                       request.post(options, (err, res, body) => {\r
+                               response = res || err;\r
+                               if(err){\r
+                                       reject(err);\r
+                               }\r
+                               if(res && res.statusCode == 200){\r
+                                       deployed = true;\r
+                                       resolve(body);\r
+                               }else{\r
+                                       reject(res);\r
+                               }\r
+                       });\r
+               }).then(\r
+                       result => {\r
+                               if(result){\r
+                                       deployedDefinition = JSON.parse(result);\r
+                               }\r
+                       }\r
+               ).catch(\r
+                       err => {\r
+                               console.log(err.body);\r
+                       }\r
+               );\r
+               if (!deployed) {\r
+                       pickleRick();\r
+                       return new Response(500, {}, { errors: { deployment: 'The bpmn file failed to deploy on the server.' } });\r
+               }\r
+\r
+               // Since test head objects are sent, we only store the test head id. this for loop adds those to the object to save\r
+               // for (let i = 0; i < data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].testHeads.length; i++) {\r
+               //      data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].testHeads[i].testHeadId = data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].testHeads[i].testHead._id;\r
+               // }\r
+\r
+               // let td = await this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].create(data.testDefinition, params)\r
+               //     .then(result => {\r
+               //         return result['data'];\r
+               //     })\r
+               //     .catch(err => {\r
+               //         console.log(err);\r
+               //     }\r
+               // Set as deployed\r
+               delete params.query;\r
+\r
+               //check to see if the process definition Key was set\r
+               // if (!data.testDefinition.processDefinitionKey) {\r
+               //      data.testDefinition.processDefinitionKey = validated.body.processDefinitionKey;\r
+               // }\r
+               let td = await this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].patch(data.testDefinition._id, {\r
+                       $set:{\r
+                               ['bpmnInstances.' + data.testDefinition.currentVersion + '.isDeployed']: true,\r
+                               ['bpmnInstances.' + data.testDefinition.currentVersion + '.processDefinitionId']: deployedDefinition['processDefinitionId'],\r
+                               ['bpmnInstances.' + data.testDefinition.currentVersion + '.deploymentId']: deployedDefinition['deploymentId']\r
+                       }\r
+               }, params)\r
+                       .then(result => {\r
+                               return result;\r
+                       })\r
+                       .catch(err => {\r
+                               logger.error(err);\r
+                       });\r
+\r
+               return new Response(200, {}, {\r
+                       //bpmnVthTaskIds: validated.body.bpmnVthTaskIds,\r
+                       //errors: validated.body.errors,\r
+                       testDefinition: td\r
+               });\r
+       }\r
+\r
+       async update(id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async patch(id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove(id, params) {\r
+               return { id };\r
+       }\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.hooks.js b/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.hooks.js
new file mode 100644 (file)
index 0000000..614d368
--- /dev/null
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { permissions } = require('../../hooks/permissions/permissions');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.service.js b/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.service.js
new file mode 100644 (file)
index 0000000..ae38535
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const createService = require('./bpmn-upload.class.js');\r
+const hooks = require('./bpmn-upload.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'bpmn-upload', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'bpmn-upload');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.class.js b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.class.js
new file mode 100644 (file)
index 0000000..8b969bc
--- /dev/null
@@ -0,0 +1,297 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const axios = require('axios');\r
+const Response = require('http-response-object');\r
+const logger = require('../../../lib/logger');\r
+const util = require('../../../lib/otf-util');\r
+const beautify = require('json-beautify');\r
+const Bpmn = require('./bpmn.class');\r
+const Readable = require('stream').Readable;\r
+const request = require('request');\r
+\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       async find (params) {\r
+               return [];\r
+       }\r
+\r
+       // Check process definition key to see if unique\r
+       async get (id, params) {\r
+               let errors = {};\r
+               // Get List of Definition keys from Camunda\r
+               let options = {\r
+                       url: this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,\r
+                       headers: {\r
+                               'Authorization': 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')\r
+                       },\r
+                       rejectUnauthorized: false\r
+               };\r
+\r
+               return await new Promise(async (resolve, reject) => {\r
+                       request.get(options, (err, response, body) => {\r
+                               if(err){\r
+                                       reject(err);\r
+                               }\r
+                               resolve(response);\r
+                       });\r
+               })\r
+               .then(\r
+                       result => {\r
+                               if (result.statusCode == 200) {\r
+                                       //check to make sure they have access\r
+                                       params.query.$limit = '-1';\r
+                                       params.query.processDefinitionKey = id;\r
+                                       return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(\r
+                                               res => {\r
+                                                       if(res.length > 0){\r
+                                                               return new Response(200, {}, res);\r
+                                                       }else{\r
+                                                               let resp = new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}});\r
+                                                               return resp;\r
+                                                       }\r
+                                               }\r
+                                       );\r
+                               }else{\r
+                                       return new Response(200, {});\r
+                               }\r
+                       }\r
+               ).catch(err => {\r
+                       return new Response(400, {});\r
+               });\r
+\r
+               // return await axios.get(\r
+               //      this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,\r
+               //      {\r
+               //              headers: {\r
+               //                      Authorization: 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')\r
+               //              }\r
+               //      })\r
+               //      .then(result => {\r
+               //              console.log(result);\r
+               //              if (result.status === 200) {\r
+               //                      //check to make sure they have access\r
+               //                      params.query.$limit = '-1';\r
+               //                      params.query.processDefinitionKey = id;\r
+               //                      return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(\r
+               //                              res => {\r
+               //                                      console.log('res 1');\r
+               //                                      console.log(res);\r
+               //                                      if(res.length > 0){\r
+               //                                              return new Response(200, {}, res);\r
+               //                                      }else{\r
+\r
+               //                                              console.log('err 1');\r
+               //                                              let resp = new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}});\r
+               //                                              console.log(resp);\r
+               //                                              return resp;\r
+               //                                      }\r
+               //                              }\r
+               //                      );\r
+               //              }else{\r
+               //                      console.log('not 200')\r
+               //                      return new Response(400, {}, {errors: errors});\r
+               //              }\r
+               //      })\r
+               //      .catch(err => {\r
+               //              return new Response(200, {});\r
+               //      });\r
+       }\r
+       // async get (id, params) {\r
+       //      console.log("bpmn-upload: get")\r
+       //      let errors = {};\r
+       //      // Get List of Definition keys from Camunda\r
+\r
+       //      // let options = {\r
+       //      //      url: this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,\r
+       //      //      headers: {\r
+       //      //              'Authorization': 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')\r
+       //      //      },\r
+       //      //      rejectUnauthorized: false\r
+       //      // }\r
+\r
+       //      // return await new Promise((resolve, reject) => {\r
+       //      //      request.post(options, (err, res, body) => {\r
+       //      //              if(err){\r
+       //      //                      reject(err);\r
+       //      //              }\r
+       //      //              resolve(res);\r
+       //      //      });\r
+       //      // }).then(\r
+       //      //      result => {\r
+       //      //              console.log(result);\r
+       //      //              if (result.statusCode === 200) {\r
+       //      //                      //check to make sure they have access\r
+       //      //                      params.query.$limit = '-1';\r
+       //      //                      params.query.processDefinitionKey = id;\r
+       //      //                      return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(\r
+       //      //                              res => {\r
+       //      //                                      return new Response(200, {}, res);\r
+       //      //                              },\r
+       //      //                              err => {\r
+       //      //                                      return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})\r
+       //      //                              }\r
+       //      //                      );\r
+       //      //              }else if(result.statusCode == 404){\r
+       //      //                      return new Response(400, {}, {errors: errors});\r
+       //      //              }else{\r
+       //      //                      return new Response(result.statusCode, {}, {errors: errors});\r
+       //      //              }\r
+       //      //      }\r
+       //      // ).catch(\r
+       //      //      err => {\r
+       //      //              console.log("Err: " + err)\r
+       //      //              //return new Response(200, {});\r
+       //      //              let newParams = Object.assign({}, params);\r
+       //      //              newParams.query.$limit = -1;\r
+       //      //              newParams.query.processDefinitionKey = id;\r
+       //      //              //console.log(params);\r
+       //      //              return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(newParams).then(\r
+       //      //                      res => {\r
+       //      //                              //return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})\r
+       //      //                              return new Response(200, {}, res);\r
+       //      //                      },\r
+       //      //                      err => {\r
+       //      //                              return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})\r
+       //      //                      }\r
+       //      //              );\r
+       //      //      }\r
+       //      // );\r
+\r
+       //      return await axios.get(\r
+       //              this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,\r
+       //              {\r
+       //                      headers: {\r
+       //                              Authorization: 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')\r
+       //                      }\r
+       //              })\r
+       //              .then(result => {\r
+       //                      console.log(result);\r
+       //                      if (result.status === 200) {\r
+       //                              console.log('in here')\r
+       //                              //check to make sure they have access\r
+       //                              params.query.$limit = '-1';\r
+       //                              params.query.processDefinitionKey = id;\r
+       //                              return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(\r
+       //                                      res => {\r
+       //                                              return new Response(200, {}, res);\r
+       //                                      }\r
+       //                              ).catch(err => {\r
+       //                                      console.log('err')\r
+       //                                      return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})\r
+       //                              });\r
+       //                      }else if(result.status === 404){\r
+       //                              console.log('or here')\r
+       //                              return new Response(400, {}, {errors: errors});\r
+       //                      }else{\r
+       //                              return new Response(result.status, {}, {errors: errors});\r
+       //                      }\r
+       //              })\r
+       //              .catch(err => {\r
+       //                      console.log("Err: " + err)\r
+       //                      //return new Response(200, {});\r
+       //                      let newParams = Object.assign({}, params);\r
+       //                      newParams.query.$limit = -1;\r
+       //                      newParams.query.processDefinitionKey = id;\r
+       //                      //console.log(params);\r
+       //                      return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(newParams).then(\r
+       //                              res => {\r
+       //                                      //return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})\r
+       //                                      return new Response(200, {}, res);\r
+       //                              }\r
+       //                      ).catch(err => {\r
+       //                              console.log('err 2')\r
+       //                              return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})\r
+       //                      });\r
+       //              });\r
+       // }\r
+\r
+       async create (data, params) {\r
+               let bpmn = new Bpmn(this.options.app, data, params);\r
+               return await bpmn.validate();\r
+       }\r
+\r
+       //validates then saves bpmn file and returns file meta data\r
+       async update (id, data, params) {\r
+               let bpmn = new Bpmn(this.options.app, data, params);\r
+               let res = await bpmn.validate();\r
+               if(res.statusCode != 200){\r
+                       return res;\r
+               }\r
+               \r
+               let b = new Buffer(res.body.bpmnXml);\r
+               let r = new Readable();\r
+               r.push(b);\r
+               r.push(null);\r
+               //save new bpmn file and return\r
+               let formData = {\r
+                       'file': {\r
+                               value: r.read(),\r
+                               options: {\r
+                                       filename: res.body.processDefinitionKey + '.bpmn'\r
+                               }\r
+                       }\r
+               };\r
+               let options = {\r
+                       url: 'https://localhost/' + this.options.app.get('base-path') + 'file-transfer',\r
+                       headers: {\r
+                               'Authorization': params.headers.Authorization,\r
+                               'Content-Type': "multipart/form-data"\r
+                       },\r
+                       rejectUnauthorized: false,\r
+                       formData: formData\r
+               }\r
+\r
+               return await new Promise((resolve, reject) => {\r
+                       request.post(options, (err, res, body) => {\r
+                               if(err){\r
+                                       reject(err);\r
+                               }\r
+                               resolve(body);\r
+                       });\r
+               }).then(\r
+                       result => {\r
+                               return result;\r
+                       }\r
+               ).catch(\r
+                       err => {\r
+                               return err;\r
+                       }\r
+               );\r
+\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove (id, params) {\r
+               return { id };\r
+       }\r
+\r
+       async parseAndUpload (data, params, method) {\r
+\r
+       }\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.hooks.js b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.hooks.js
new file mode 100644 (file)
index 0000000..98dc42e
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.service.js b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.service.js
new file mode 100644 (file)
index 0000000..914fb26
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `bpmnValidate` service on path `/bpmn-validate`\r
+const createService = require('./bpmn-validate.class.js');\r
+const hooks = require('./bpmn-validate.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'bpmn-validate', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'bpmn-validate');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn.class.js b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn.class.js
new file mode 100644 (file)
index 0000000..620f9bd
--- /dev/null
@@ -0,0 +1,360 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { parseString, Builder } = require('xml2js');\r
+const logger = require('../../../lib/logger');\r
+const Response = require('http-response-object');\r
+\r
+class Bpmn {\r
+    constructor(app, data, params){\r
+               this.delegates = [\r
+                       {\r
+                               name: 'vth',\r
+                               regex: new RegExp('^(vth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),\r
+                               delegate: '${callTestHeadDelegate}'\r
+                       },\r
+                       {\r
+                               name: 'lvth',\r
+                               regex: new RegExp('^(lvth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),\r
+                               topic: 'vth'\r
+                       },\r
+                       {\r
+                               name: 'log',\r
+                               regex: new RegExp('^UTIL:LogTestResult$', 'i'),\r
+                               delegate: '${logTestResultDelegate}',\r
+                       },\r
+                       {\r
+                               name: 'pflo',\r
+                               regex: new RegExp('^PFLO(:(.+))?$', 'i'),\r
+                               delegate: '${runTestInstanceDelegate}',\r
+                               topic: 'testInstance'\r
+                       },\r
+                       {\r
+                               name: 'SubFlow',\r
+                               regex: new RegExp('^SUBFLOW(:(.+))?$', 'i'),\r
+                               delegate: '${runTestInstanceDelegate}',\r
+                               topic: 'testInstance'\r
+                       },\r
+                       {\r
+                               name: 'dmaap',\r
+                               regex: new RegExp('^PostResultsToDMaaP(:(.+))?$', 'i'),\r
+                               delegate: '${postResultsToDMaaPDelegate}'\r
+                       },\r
+                       {\r
+                               name: 'mail',\r
+                               regex: new RegExp('^UTIL:SendMail(:(.+))?$', 'i'),\r
+                               delegate: '${sendMailDelegate}'\r
+                       }\r
+               ];\r
+\r
+               this.serviceTasksNotAllowed = [\r
+                       {\r
+                               key: 'camunda:class'\r
+                       }\r
+               ]\r
+        \r
+        this.params = params;\r
+        this.data = data;\r
+        this.app = app;\r
+               this.parsedXMLtoJSON = null;\r
+               this.bpmnVthTaskIds = [];\r
+               this.bpmnPfloTaskIds = [];\r
+               this.processDefinitionKey = null;\r
+               this.errors = {};\r
+               this.hasLog = false; //1 log is required in each workflow\r
+               this.hasTestHeads = false;\r
+               \r
+    }\r
+\r
+    async validate(){\r
+               //convert bpmn to json\r
+               //console.log(this.data.testDefinition);\r
+               parseString(\r
+                       this.data.testDefinition.bpmnInstances[this.data.testDefinition.currentVersion].bpmnXml,\r
+                       (err, result) => {\r
+                               if (err) {\r
+                                       logger.error(err);\r
+                               }\r
+                               this.parsedXMLtoJSON = Object.assign({}, result);\r
+            }\r
+               );\r
+\r
+        //If the bpmn was unable to be parsed, return error response\r
+        if (!this.parsedXMLtoJSON) {\r
+                       return new Response(500, {}, { errors: { parsingError: { error: 'Failed to parse bpmn. Try Again.' } } });\r
+        }\r
+\r
+        //set temp process\r
+               var process = this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0];\r
+\r
+\r
+               // Not needed with new modeler\r
+        //If a process definition key was sent with the requrest, use it instead\r
+               if (this.data.testDefinition.processDefinitionKey && this.data.testDefinition.processDefinitionKey != '') {\r
+                       this.processDefinitionKey = this.data.testDefinition.processDefinitionKey;\r
+               }else{\r
+                   this.processDefinitionKey = process.$.id;\r
+        }\r
+        \r
+        //Check to see if the process definition key is unique\r
+        let key = await this.app.services[this.app.get('base-path') + 'bpmn-validate'].get(this.processDefinitionKey, this.params).then();\r
+        if(key.statusCode != 200 && key.errors && key.errors.processDefinitionKey){\r
+            this.errors.processDefinitionKey = {\r
+                error: 'Process Definition Key has already been used',\r
+                key: this.processDefinitionKey\r
+            };\r
+        }\r
+        \r
+        // Verify start task(s) are async. Only start task(s) in main process\r
+               if (process['bpmn:startEvent']) {\r
+                       for (var j = 0; j < process['bpmn:startEvent'].length; j++) {\r
+                               var startEvent = process['bpmn:startEvent'][j];\r
+                               if (startEvent.$['camunda:asyncBefore'] != 'true') {\r
+                                       this.errors.startEvent = { error: 'Start Event, ' + startEvent.$.id + ', is not async' };\r
+                               }\r
+                       }\r
+               } else {\r
+                       this.errors.startEvent = { error: 'Workflow must have a start even' };\r
+        }\r
+        \r
+        //Find all of the task boxes that need to be changed (recursive)\r
+        await this.findTasks(this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0]);\r
+        \r
+        // If log task did not exist, log\r
+               if (!this.hasLog) {\r
+                       this.errors.required = { error: 'No LogSetResult task. One is required.' };\r
+        }\r
+        \r
+        // If errors, return them before creating an instance in the database\r
+               if (\r
+                       this.errors.processDefinitionKey ||\r
+                       this.errors.notFound ||\r
+                       this.errors.testHead ||\r
+                       this.errors.permissions ||\r
+                       this.errors.required ||\r
+                       this.errors.startEvent\r
+               ) {\r
+                       return new Response(400, {}, { bpmnVthTaskIds: this.bpmnVthTaskIds, errors: this.errors });\r
+               }\r
+\r
+        //set the new process Definition key\r
+        //console.log('END Process Key: ' + this.processDefinitionKey);\r
+               this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0].$.id = this.processDefinitionKey;\r
+\r
+        //build xml from the json object\r
+               var xmlBuilder = new Builder();\r
+        var xmlToSend = xmlBuilder.buildObject(this.parsedXMLtoJSON);\r
+        \r
+               //console.log(this.bpmnVthTaskIds);\r
+               \r
+               let response = { \r
+                       bpmnXml: xmlToSend, \r
+                       bpmnVthTaskIds: this.bpmnVthTaskIds, \r
+                       bpmnPfloTaskIds: this.bpmnPfloTaskIds, \r
+                       processDefinitionKey: this.processDefinitionKey, \r
+               };\r
+\r
+               //if there are errors\r
+               if(JSON.stringify(this.errors) != "{}"){\r
+                       response.errors = this.errors\r
+               }\r
+\r
+               return new Response(200, {}, response);\r
+        \r
+    }\r
+\r
+    async findTasks (process) {\r
+               //If there are service tasks in the diagram\r
+               if(process['bpmn:serviceTask']){\r
+                       //console.log('has service task');\r
+                       // Go through all of the service task boxes\r
+                       for (let j = 0; j < process['bpmn:serviceTask'].length; j++) {\r
+                               //console.log(process['bpmn:serviceTask'][j])\r
+\r
+                               //check that the service task is not on the DO NOT ALLOW list\r
+                               for(let n = 0; n < this.serviceTasksNotAllowed.length; n++){\r
+                                       //check cammunda keys\r
+                                       if(process['bpmn:serviceTask'][j].$[this.serviceTasksNotAllowed[n].key]){\r
+                                               if(!this.errors.permissions){\r
+                                                       this.errors.permissions = [];\r
+                                               }\r
+                                               this.errors.permissions.push({error: this.serviceTasksNotAllowed[n].key + ' is not allowed.'})\r
+                                       }\r
+                               }\r
+\r
+                               //Clear any user defined delegate expressions\r
+                               if(process['bpmn:serviceTask'][j].$['camunda:delegateExpression']){\r
+                                       process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = '';\r
+                               }\r
+                               \r
+                               //Go through all the delegates that are defined by OTF (in constructor)\r
+                               for (let d = 0; d < this.delegates.length; d++){\r
+                                       var match = null;\r
+                                       \r
+                                       if(match = process['bpmn:serviceTask'][j].$.name.match(this.delegates[d].regex)){\r
+                                               //console.log(match)\r
+                                               switch(this.delegates[d].name){\r
+                                                       case 'vth':\r
+                                                       case 'cvth':\r
+                                                       case 'lvth':\r
+                                                               await this.checkTestHead(match, process['bpmn:serviceTask'][j]);\r
+                                                               break;\r
+\r
+                                                       case 'pflo':\r
+                                                               let temp = {bpmnPfloTaskId: process['bpmn:serviceTask'][j].$.id};\r
+                                                               if(match[2]){\r
+                                                                       temp['label'] = match[2];\r
+                                                               }\r
+                                                               this.bpmnPfloTaskIds.push(temp);\r
+                                                               break;\r
+                                                       \r
+                                                       case 'log':\r
+                                                               this.hasLog = true;\r
+                                                               break;\r
+                                                       \r
+                                               }\r
+\r
+                                               if(this.delegates[d].topic){\r
+                                                       process['bpmn:serviceTask'][j].$['camunda:type'] = 'external';\r
+                                                       process['bpmn:serviceTask'][j].$['camunda:topic'] = this.delegates[d].topic;\r
+                                               }else{\r
+                                                       process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = this.delegates[d].delegate;\r
+                                               }\r
+\r
+                                               break;\r
+\r
+                                       }\r
+                               }\r
+\r
+                       }\r
+               } //end if service task\r
+\r
+               if(process['bpmn:task']){\r
+                       //console.log('has task')\r
+                       //init service task array \r
+                       if(!process['bpmn:serviceTask']){\r
+                               process['bpmn:serviceTask'] = [];\r
+                       }\r
+\r
+                       // Go through all of the task boxes\r
+                       for (let j = 0; j < process['bpmn:task'].length; j++) {\r
+                               //console.log(process['bpmn:task'][j])\r
+\r
+                               for (let d = 0; d < this.delegates.length; d++){\r
+                                       var match = null;\r
+                                       \r
+                                       if(match = process['bpmn:task'][j].$.name.match(this.delegates[d].regex)){\r
+                                               //console.log(match)\r
+                                               switch(this.delegates[d].name){\r
+                                                       case 'vth':\r
+                                                       case 'cvth':\r
+                                                       case 'lvth':\r
+                                                               await this.checkTestHead(match, process['bpmn:task'][j]);\r
+                                                               break;\r
+                                                       \r
+                                                       case 'pflo':\r
+                                                               let temp = {bpmnPfloTaskId: process['bpmn:task'][j].$.id};\r
+                                                               if(match[2]){\r
+                                                                       temp['label'] = match[2];\r
+                                                               }\r
+                                                               this.bpmnPfloTaskIds.push(temp);\r
+                                                               break;\r
+                                                       \r
+                                                       case 'log':\r
+                                                               this.hasLog = true;\r
+                                                               break;\r
+                                                       \r
+                                               }\r
+\r
+                                               let task = {\r
+                                                       $: {\r
+                                                               id: process['bpmn:task'][j].$.id,\r
+                                                               name: process['bpmn:task'][j].$.name,\r
+                                                       },\r
+                                                       'bpmn:incoming': process['bpmn:task'][j]['bpmn:incoming'],\r
+                                                       'bpmn:outgoing': process['bpmn:task'][j]['bpmn:outgoing']\r
+                                               }\r
+\r
+                                               if(this.delegates[d].topic){\r
+                                                       task.$['camunda:type'] = 'external';\r
+                                                       task.$['camunda:topic'] = this.delegates[d].topic;\r
+                                               }else{\r
+                                                       task.$['camunda:delegateExpression'] = this.delegates[d].delegate;\r
+                                               }\r
+\r
+                                               process['bpmn:serviceTask'].push(task);\r
+\r
+                                               process['bpmn:task'].splice(j, 1);\r
+                                               j--;\r
+\r
+                                               break;\r
+\r
+                                       }\r
+                               }\r
+\r
+                       }\r
+\r
+               }\r
+\r
+               //If subprocess, find tasks\r
+               if(process['bpmn:subProcess']){\r
+                       for(let p = 0; p < process['bpmn:subProcess'].length; p++){\r
+                               await this.findTasks(process['bpmn:subProcess'][p]);\r
+                       }\r
+               }\r
+               \r
+       }\r
+\r
+       async checkTestHead(match, task){\r
+               if (match.length >= 4) {\r
+                       match[3] = '^' + match[3] + '$';\r
+                       this.params.query = { testHeadName: new RegExp(match[3], 'i')};\r
+                       delete this.params.paginate;\r
+                       //console.log(this.params);\r
+                       await this.app.services[this.app.get('base-path') + 'test-heads'].find(this.params)\r
+                               .then(result => {\r
+                                       if (result.total > 1) {\r
+                                               // there should only be one test head found, else there is an error in the database\r
+                                               if (!this.errors.testHeads) {\r
+                                                       this.errors.testHeads = [];\r
+                                               }\r
+                                               this.errors.testHeads.push({ error: result.total + ' test heads named: ' + match[3] });\r
+                                       }\r
+\r
+                                       if (result.total == 0) {\r
+                                               if(!this.errors.permissions){\r
+                                                       this.errors.permissions = []\r
+                                               }\r
+                                               this.errors.permissions.push({ error: 'You do not have access to test head: ' + match[3] });\r
+                                       } else {\r
+                                               this.bpmnVthTaskIds.push({ testHead: result.data[0], bpmnVthTaskId: task.$.id });\r
+                                       }\r
+                               })\r
+                               .catch(err => {\r
+                                       //console.log(err);\r
+                                       this.errors.notFound = { error: 'Test head "' + match[3] + '" does not exist' };\r
+                               });\r
+               } else if (match.length > 0) { // no test head name supplied\r
+\r
+               }\r
+       }\r
+\r
+}\r
+module.exports = function (app, data, params) {\r
+       return new Bpmn(app, data, params);\r
+};\r
+\r
+module.exports.Bpmn = Bpmn;
\ No newline at end of file
diff --git a/otf-frontend/server/src/feathers/services/execute/execute.class.js b/otf-frontend/server/src/feathers/services/execute/execute.class.js
new file mode 100644 (file)
index 0000000..f5b6867
--- /dev/null
@@ -0,0 +1,132 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const request = require('request');\r
+const Response = require('http-response-object');\r
+const logger = require('../../../lib/logger');\r
+const util = require('../../../lib/otf-util');\r
+const errors = require('@feathersjs/errors');\r
+\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       async find (params) {\r
+               return [];\r
+    }\r
+    \r
+       async get (id, params) {\r
+\r
+       }\r
+\r
+       async create (data, params) {\r
+               \r
+        let id = data._id;\r
+               delete data._id;\r
+               delete data.createdBy;\r
+               \r
+        let options = {\r
+                       url: this.options.app.get('serviceApi').url + 'testInstance/execute/v1/id/' + id,\r
+                       headers: {\r
+                               'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),\r
+                               'Content-Type': "application/json"\r
+                       },\r
+            rejectUnauthorized: false,\r
+            body: JSON.stringify(data)\r
+               }\r
+\r
+               return await new Promise((resolve, reject) => {\r
+                       request.post(options, (err, res, body) => {\r
+                               if(err){\r
+                                       reject(err);\r
+                               }\r
+                               if(res.body){\r
+                                       res.body = JSON.parse(res.body);\r
+                                       if(res.body.statusCode != 200){\r
+                                               reject(res.body);\r
+                                       }\r
+                                       resolve(res.body);\r
+                               }else{\r
+                                       reject(res);\r
+                               }\r
+                               \r
+                       });\r
+               }).then(\r
+                       res => {\r
+                return res;\r
+            }\r
+               ).catch(\r
+                       err => {\r
+                               return err;\r
+                       }\r
+               );\r
+       }\r
+\r
+       async update (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove (id, params) {\r
+\r
+               let execution = await this.options.app.services[this.options.app.get('base-path') + 'test-executions'].get(id, { query: { $select: ['processInstanceId']}});\r
+               \r
+               if(!execution.processInstanceId){\r
+                       throw new errors.GeneralError('Could not find the execution process instance id');\r
+               }\r
+\r
+               let options = {\r
+                       url: this.options.app.get('camundaApi').url + 'otf/tcu/delete-process-instance/v1/' + execution.processInstanceId,\r
+                       headers: {\r
+                               'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),\r
+                               'Content-Type': "application/json"\r
+                       },\r
+                       rejectUnauthorized: false\r
+               }\r
+\r
+               return await new Promise((resolve, reject) => {\r
+                       request.delete(options, (err, res, body) => {\r
+                               if(err){\r
+                                       reject(err);\r
+                               }\r
+                               if(res.body){\r
+                                       res.body = JSON.parse(res.body);\r
+                               }\r
+                               resolve(res);\r
+                       });\r
+               }).then(\r
+                       res => {\r
+                               return res;\r
+                       }\r
+               ).catch(\r
+                       err => {\r
+                               console.log(err);\r
+                       }\r
+               );\r
+       }\r
+\r
+\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/execute/execute.hooks.js b/otf-frontend/server/src/feathers/services/execute/execute.hooks.js
new file mode 100644 (file)
index 0000000..f64d812
--- /dev/null
@@ -0,0 +1,93 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { permissions, limitFields } = require('../../hooks/permissions/permissions');\r
+const errors = require('@feathersjs/errors');\r
+const throwError = require('../../hooks/throw');\r
+const { disallow } = require('feathers-hooks-common');\r
+const canExecute = function(){\r
+       return async (context) => {\r
+               let id = context.id || context.data._id;\r
+               //must have an _id\r
+               if(!id){\r
+                       if(context.method == 'create')\r
+                               throw new errors.BadRequest("'_id' and 'asyncTopic' is required to execute a test instance");\r
+                       else\r
+                               throw new errors.BadRequest("An id must be provided to cancel an execution")\r
+               }\r
+\r
+               let testInstanceId = id;\r
+\r
+               if(context.method == 'remove'){\r
+                       let execution = await context.app.services[context.app.get('base-path') + 'test-executions'].get(id, {provider: undefined, query: { $select: ['historicTestInstance._id']}});\r
+                       testInstanceId = execution.historicTestInstance._id;\r
+               }\r
+\r
+               //get group id of the test instance that is being executed\r
+               let testInstance = await context.app.services[context.app.get('base-path') + 'test-instances'].get(testInstanceId, {query: { $select: ['groupId', 'testDefinitionId', 'disabled'] } });\r
+\r
+               //check if its locked\r
+               let testDefinition = await context.app.services[context.app.get('base-path') + 'test-definitions'].get(testInstance.testDefinitionId, {query: { $select: ['disabled'] } });\r
+\r
+               if((testInstance.disabled || testDefinition.disabled) && context.method == 'create'){\r
+                       throw new errors.Unavailable('The test instance or definition is locked.');\r
+               }\r
+\r
+               testInstance = new context.app.services[context.app.get('base-path') + 'test-instances'].Model(testInstance);\r
+               if(context.params.ability.cannot('execute', testInstance)){\r
+                       throw new errors.Forbidden(`You are not allowed to execute this instance.`);\r
+               }\r
+       }\r
+}\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt'), permissions('execute')],\r
+               find: [ throwError(new errors.MethodNotAllowed()) ],\r
+               get: [ throwError(new errors.MethodNotAllowed())],\r
+               create: [\r
+                       (context) => {\r
+                               context.data.executorId = context.params.user._id;\r
+                               return context;\r
+                       },\r
+                       canExecute()\r
+               ],\r
+               update: [ throwError(new errors.MethodNotAllowed()) ],\r
+               patch: [ throwError(new errors.MethodNotAllowed()) ],\r
+               remove: [canExecute()]\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/execute/execute.service.js b/otf-frontend/server/src/feathers/services/execute/execute.service.js
new file mode 100644 (file)
index 0000000..dff9b69
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `groups` service on path `/groups`\r
+const createService = require('./execute.class');\r
+const hooks = require('./execute.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               app,\r
+               paginate\r
+       };\r
+\r
+       app.use(app.get('base-path') + 'execute', createService(options));\r
+\r
+       const service = app.service(app.get('base-path') + 'execute');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/feedback/feedback.class.js b/otf-frontend/server/src/feathers/services/feedback/feedback.class.js
new file mode 100644 (file)
index 0000000..74a5a73
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       // function getLink(type, hash) {\r
+       //      const url = 'http://localhost:443/' + type + '?token=' + hash\r
+       //      return url\r
+       // }\r
+\r
+       async find (params) {\r
+               return [];\r
+       }\r
+\r
+       // Check process definition key to see if unique\r
+       async get (id, params) {\r
+\r
+       }\r
+\r
+       async create (data, params) {\r
+\r
+\r
+\r
+       }\r
+\r
+       async update (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove (id, params) {\r
+               return { id };\r
+       }\r
+\r
+\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/feedback/feedback.hooks.js b/otf-frontend/server/src/feathers/services/feedback/feedback.hooks.js
new file mode 100644 (file)
index 0000000..7820300
--- /dev/null
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { disallow } = require('feathers-hooks-common');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [ ],\r
+               find: [],\r
+               get: [],\r
+               create: [\r
+                       authenticate('jwt'),\r
+                       context => {\r
+                               let sender = context.app.get('otf').email;\r
+                               let data = context.data['data'];\r
+                               let message = data['message'];\r
+                               let user = context.params.user;\r
+\r
+                               let feedback = "Email: " + user['email'] + "</br>" +\r
+                                                               "First Name: " + user['firstName'] + "</br>" +\r
+                                                               "Last Name: " + user['lastName'] + "</br>" +\r
+                                                               "Message: " + message + "</br>" +\r
+                                                               "Date: " + new Date();\r
+                                       let email = {\r
+                                       from: sender,\r
+                                       to: sender,\r
+                                       subject: 'Feedback',\r
+                                       html: feedback\r
+                               }\r
+\r
+                               return context.app.service(context.app.get('base-path') + 'mailer').create(email).then(function (result) {\r
+                                       console.log('Sent email', result)\r
+                               }).catch(err => {\r
+                                       console.log('Error sending email: ', email, err)\r
+                               })\r
+\r
+\r
+                       }\r
+               ],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/feedback/feedback.service.js b/otf-frontend/server/src/feathers/services/feedback/feedback.service.js
new file mode 100644 (file)
index 0000000..7ae61ed
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `bpmnValidate` service on path `/bpmn-validate`\r
+const createService = require('./feedback.class.js');\r
+const hooks = require('./feedback.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'feedback', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'feedback');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.class.js b/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.class.js
new file mode 100644 (file)
index 0000000..0ac6670
--- /dev/null
@@ -0,0 +1,177 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const Response = require('http-response-object');\r
+const Readable = require('stream').Readable;\r
+const mongooseGridFS = require('mongoose-gridfs');\r
+const AdmZip = require('adm-zip');\r
+const errors = require('@feathersjs/errors');\r
+\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+               this.mongoose = this.options.app.get('mongooseClient');\r
+               this.gridfs = mongooseGridFS({\r
+                       collection: 'fs',\r
+                       model: 'File',\r
+                       mongooseConnection: this.mongoose.connection\r
+               });\r
+               this.FileModel = this.gridfs.model;\r
+       }\r
+\r
+       async find (params) {\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async get (id, params) {\r
+               let content = await this.callReadFile(id).then(res => {\r
+                       return res;\r
+               });\r
+\r
+               if(params.query && params.query.robot){\r
+                       content = await this.createRobotResponse(content);\r
+               }\r
+               return content;\r
+       }\r
+\r
+       async create (data, params) {\r
+        const files = params.files;\r
+\r
+        if (!files || files.length === 0) {\r
+            throw new BadRequest("No files found to upload")\r
+        }\r
+\r
+        let promises = [];\r
+\r
+        files.forEach(file => {\r
+            let promise = new Promise( (resolve, reject) => {\r
+\r
+                let stream = new Readable();\r
+                stream.push(file.buffer);\r
+                stream.push(null);\r
+\r
+               this.FileModel.write(\r
+                       {\r
+                               filename: file.originalname,\r
+                               contentType: file.mimeType\r
+                       },\r
+                       stream,\r
+                       function (error, savedAttachment) {\r
+                               if (error) {\r
+                                       logger.error(error);\r
+                                       reject(error);\r
+                               } else {\r
+                            stream.destroy();\r
+                            resolve(savedAttachment);\r
+                               }\r
+                    }\r
+                );\r
+\r
+            })\r
+\r
+            promises.push(promise);\r
+        });\r
+\r
+        const result = await Promise.all(promises);\r
+        \r
+        return result;\r
+       }\r
+\r
+       async update (id, data, params) {\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async remove (id, params) {\r
+               let err = await this.callUnlinkFile(id).then(err => {\r
+            return err;\r
+        });\r
+\r
+        if(err){\r
+            throw errors.GeneralError(err);\r
+        }        \r
+\r
+        return new Response(200, {});\r
+       }\r
+\r
+       readFile (id) {\r
+               return new Promise(resolve => {\r
+                       // FileModel.readById(context.id, (err, content) => resolve(content));\r
+                       let stream = this.FileModel.readById(id);\r
+       \r
+                       stream.on('error', (err) => resolve(err));\r
+                       stream.on('data', (content) => resolve(content));\r
+                       stream.on('close', (res) => resolve(res));\r
+                       // api.on(event, response => resolve(response));\r
+               });\r
+       }\r
+       \r
+       async callReadFile (id) {\r
+               return this.readFile(id);\r
+       }\r
+       \r
+       async createRobotResponse(content){\r
+       \r
+               let re;\r
+       \r
+               await new Promise((resolve, reject) => {\r
+                       let newObj = {};\r
+                       let read = new Readable();\r
+                       read.push(content);\r
+                       read.push(null);\r
+                       let z = new AdmZip(content);\r
+                       let entries = z.getEntries();\r
+                       entries.forEach(zipEntry => {\r
+                               newObj[zipEntry.name] = zipEntry.getData().toString('utf8');\r
+                               // console.log(zipEntry.toString()); // outputs zip entries information\r
+                               // console.log(zipEntry.getData().toString('utf8')); \r
+                       });\r
+                       resolve(newObj);\r
+               }).then(res => {\r
+                       re = res;\r
+                       //console.log(re);\r
+               }).catch(err => {\r
+                       console.log(err);\r
+               });\r
+       \r
+               return re;\r
+       }\r
+\r
+       unlinkFile(id) {\r
+       \r
+               return new Promise(resolve => {\r
+                       \r
+                       //FileModel.readById(context.id, (err, content) => resolve(content));\r
+                       this.FileModel.unlinkById(id, (err) => resolve(err));\r
+                       //api.on(event, response => resolve(response));\r
+               });\r
+       }\r
+       \r
+       async callUnlinkFile(id) {\r
+               return await this.unlinkFile(id);\r
+       }\r
+}\r
+\r
+\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.hooks.js b/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.hooks.js
new file mode 100644 (file)
index 0000000..8e48bfe
--- /dev/null
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { disallow } = require('feathers-hooks-common');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [disallow()],\r
+               get: [],\r
+               create: [],\r
+               update: [disallow()],\r
+               patch: [disallow()],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.service.js b/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.service.js
new file mode 100644 (file)
index 0000000..961bde4
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const createService = require('./file-transfer.class');\r
+const hooks = require('./file-transfer.hooks');\r
+\r
+const multipartMiddleware = require('multer')();\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               name: 'files',\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(\r
+               app.get('base-path') + 'file-transfer',\r
+\r
+               multipartMiddleware.any(),\r
+\r
+               function (req, res, next) {\r
+                       req.feathers.files = req.files;\r
+                       next();\r
+               },\r
+\r
+               createService(options)\r
+       );\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'file-transfer');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/files/files.hooks.js b/otf-frontend/server/src/feathers/services/files/files.hooks.js
new file mode 100644 (file)
index 0000000..21d17ac
--- /dev/null
@@ -0,0 +1,52 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { disallow } = require('feathers-hooks-common');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [],\r
+               get: [],\r
+               create: [\r
+                       disallow()\r
+               ],\r
+               update: [disallow()],\r
+               patch: [disallow()],\r
+               remove: [disallow()]\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/files/files.service.js b/otf-frontend/server/src/feathers/services/files/files.service.js
new file mode 100644 (file)
index 0000000..0fc3476
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/file.model');\r
+const hooks = require('./files.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+\r
+       const options = {\r
+               Model\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'files', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'files');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/groups/groups.hooks.js b/otf-frontend/server/src/feathers/services/groups/groups.hooks.js
new file mode 100644 (file)
index 0000000..c570596
--- /dev/null
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { groupFilter } = require('../../hooks/filters.js');\r
+const { permissions } = require('../../hooks/permissions/permissions');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [ groupFilter() ],\r
+               get: [ groupFilter() ],\r
+               create: [ permissions('groups') ],\r
+               update: [ permissions('groups') ],\r
+               patch: [ permissions('groups') ],\r
+               remove: [ permissions('groups') ]\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/groups/groups.service.js b/otf-frontend/server/src/feathers/services/groups/groups.service.js
new file mode 100644 (file)
index 0000000..bee071b
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `groups` service on path `/groups`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/groups.model');\r
+const hooks = require('./groups.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               paginate\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'groups', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'groups');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/health/health.class.js b/otf-frontend/server/src/feathers/services/health/health.class.js
new file mode 100644 (file)
index 0000000..28dfcdf
--- /dev/null
@@ -0,0 +1,106 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const Response = require('http-response-object');\r
+const request = require('request');\r
+\r
+/* eslint-disable no-unused-vars */\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       async find (params) {\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async get (id, params) {\r
+               if(id == 'tcu-engine'){\r
+                       let options = {\r
+                               url: this.options.app.get('camundaApi').url + 'otf/health/v1',\r
+                               rejectUnauthorized: false\r
+                       }\r
+                       \r
+                       return await new Promise((resolve, reject) => {\r
+                               request.get(options, (err, res, body) => {\r
+                                       if(err){\r
+                                               reject(err);\r
+                                       }\r
+                                       resolve(res);\r
+                               });\r
+                       }).then(\r
+                               res => {\r
+                                       return res;\r
+                               }\r
+                       ).catch(\r
+                               err => {\r
+                                       return new Response(500, {}, err);\r
+                               }\r
+                       );\r
+               }else if(id == 'tcu-api'){\r
+                       let options = {\r
+                               url: this.options.app.get('serviceApi').url + 'health/v1',\r
+                               rejectUnauthorized: false\r
+                       }\r
+                       \r
+                       return await new Promise((resolve, reject) => {\r
+                               request.get(options, (err, res, body) => {\r
+                                       if(err){\r
+                                               reject(err);\r
+                                       };\r
+                                       resolve(res);\r
+                               });\r
+                       }).then(\r
+                               res => {\r
+                                       return res;\r
+                               }\r
+                       ).catch(\r
+                               err => {\r
+                                       return new Response(500, {}, err);\r
+                               }\r
+                       );\r
+               }else{\r
+                       return new Response(200, {});\r
+               }\r
+               \r
+       }\r
+\r
+       async create (data, params) {\r
+               if (Array.isArray(data)) {\r
+                       return Promise.all(data.map(current => this.create(current, params)));\r
+               }\r
+\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async update (id, data, params) {\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return new Response(200, {});\r
+       }\r
+\r
+       async remove (id, params) {\r
+               return new Response(200, {});\r
+       }\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/health/health.hooks.js b/otf-frontend/server/src/feathers/services/health/health.hooks.js
new file mode 100644 (file)
index 0000000..ad4601b
--- /dev/null
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/health/health.service.js b/otf-frontend/server/src/feathers/services/health/health.service.js
new file mode 100644 (file)
index 0000000..8b66fd0
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `health` service on path `/health`\r
+const createService = require('./health.class.js');\r
+const hooks = require('./health.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('path') + 'health/v1', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('path') + 'health/v1');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/index.js b/otf-frontend/server/src/feathers/services/index.js
new file mode 100644 (file)
index 0000000..cccf259
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const users = require('./users/users.service.js');\r
+const groups = require('./groups/groups.service.js');\r
+const testHeads = require('./test-heads/test-heads.service.js');\r
+const testInstances = require('./test-instances/test-instances.service.js');\r
+const testExecutions = require('./test-executions/test-executions.service.js');\r
+const testDefinitions = require('./test-definitions/test-definitions.service.js');\r
+const jobs = require('./jobs/jobs.service.js');\r
+const health = require('./health/health.service.js');\r
+const bpmnUpload = require('./bpmn-upload/bpmn-upload.service.js');\r
+const bpmnValidate = require('./bpmn-validate/bpmn-validate.service.js');\r
+const testExecutionStatus = require('./test-execution-status/test-execution-status.service.js');\r
+const testExecutionController = require('../../agenda/controllers/test-execution-controller');\r
+const mailer = require('./mailer/mailer.service.js');\r
+const authManagement = require('./auth-management/auth-management.service.js');\r
+const feedback = require('./feedback/feedback.service.js');\r
+const fileTransfer = require('./file-transfer/file-transfer.service.js');\r
+const files = require('./files/files.service.js');\r
+const execute = require('./execute/execute.service.js');\r
+const messages = require('./messages/messages.service')\r
+\r
+module.exports = function (app) {\r
+       app.configure(users);\r
+       app.configure(files);\r
+       app.configure(fileTransfer)\r
+       app.configure(groups);\r
+       app.configure(testHeads);\r
+       app.configure(testInstances);\r
+       app.configure(testExecutions);\r
+       app.configure(testDefinitions);\r
+       app.configure(execute);\r
+       app.configure(messages);\r
+       app.configure(jobs);\r
+       app.configure(health);\r
+       app.configure(bpmnUpload);\r
+       app.configure(bpmnValidate);\r
+       app.configure(testExecutionStatus);\r
+       app.configure(testExecutionController);\r
+       app.configure(mailer);\r
+       app.configure(authManagement);\r
+       app.configure(feedback);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/jobs/jobs.class.js b/otf-frontend/server/src/feathers/services/jobs/jobs.class.js
new file mode 100644 (file)
index 0000000..1571dc1
--- /dev/null
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
diff --git a/otf-frontend/server/src/feathers/services/jobs/jobs.hooks.js b/otf-frontend/server/src/feathers/services/jobs/jobs.hooks.js
new file mode 100644 (file)
index 0000000..f119833
--- /dev/null
@@ -0,0 +1,218 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { iff, disallow } = require('feathers-hooks-common');\r
+const logger = require('../../../lib/logger');\r
+const request = require('request-promise');\r
+const agendaJobPopulate = require('../../hooks/agendaJobPopulate');\r
+const utils = require('../../../lib/otf-util');\r
+const axios = require('axios');\r
+const util = require('../../../lib/otf-util');\r
+const checkLocks = require('../../hooks/checkLocks');\r
+const { permissions, limitFields } = require('../../hooks/permissions/permissions');\r
+const errors = require('@feathersjs/errors');\r
+\r
+const canExecute = function(){\r
+       return async (context) => {\r
+               let id = context.id || context.data.testInstanceId;\r
+\r
+               let testInstanceId = id;\r
+\r
+               if(context.method == 'remove'){\r
+                       let job = await context.app.services[context.app.get('base-path') + 'jobs'].get(id, {provider: undefined});\r
+                       console.log(job)\r
+                       testInstanceId = job.data.testSchedule._testInstanceId;\r
+                       console.log(testInstanceId)\r
+               }\r
+\r
+               //get group id of the test instance that is being executed\r
+               let testInstance = await context.app.services[context.app.get('base-path') + 'test-instances'].get(testInstanceId, {provider: undefined, query: { $select: ['groupId', 'testDefinitionId', 'disabled'] } });\r
+\r
+               //check if its locked\r
+               let testDefinition = await context.app.services[context.app.get('base-path') + 'test-definitions'].get(testInstance.testDefinitionId, {provider: undefined, query: { $select: ['disabled'] } });\r
+\r
+               if((testInstance.disabled || testDefinition.disabled) && context.method == 'create'){\r
+                       throw new errors.Unavailable('The test instance or definition is locked.');\r
+               }\r
+\r
+               testInstance = new context.app.services[context.app.get('base-path') + 'test-instances'].Model(testInstance);\r
+               if(context.params.ability.cannot('execute', testInstance)){\r
+                       throw new errors.Forbidden(`You are not allowed to execute this instance.`);\r
+               }\r
+       }\r
+}\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [\r
+                       async function(context){\r
+                               if(context.params.query.testInstanceId){\r
+                                       const mongoose = context.app.get('mongooseClient');\r
+                                       const toObjectId = v => mongoose.Types.ObjectId(v);\r
+\r
+                                       let Model = context.app.service(context.app.get('base-path') + 'jobs').Model;\r
+                                       const conditions = [{\r
+                                               $match:{\r
+                                                        "data.testSchedule._testInstanceId": toObjectId(context.params.query.testInstanceId),\r
+                                                        "nextRunAt":  {\r
+                                                                $ne: null\r
+                                                        }//{\r
+                                                       //      "testSchedule": {\r
+                                                       //              "_testInstanceId": toObjectId(context.params.query.testInstanceId)\r
+                                                       //      }\r
+                                                       // }\r
+                                               }\r
+                                       }];\r
+\r
+                                       await new Promise(function(resolve, reject){\r
+                                               Model.aggregate(conditions).exec(function(error, result){\r
+                                                       if(error){\r
+                                                               reject(error);\r
+                                                       }\r
+                                                       resolve(result);\r
+                                               });\r
+                                       }).then(result => {\r
+                                               if(result.length){\r
+                                                       if(result.length == 1){\r
+                                                               context.params.query._id = result[0]._id;\r
+                                                       }else if(result.length == 0){\r
+                                                               //do nothing\r
+                                                       }else{\r
+                                                               let ids = [];\r
+                                                               result.forEach(elem => {\r
+                                                                       ids.push(elem._id);\r
+                                                               });\r
+                                                               context.params.query._id = {\r
+                                                                       $in: ids\r
+                                                               }\r
+                                                       }\r
+                                               }else{\r
+                                                       context.params.query._id = result._id;\r
+                                               }\r
+                                       }).catch(err => {\r
+                                               console.log(err);\r
+                                       });\r
+\r
+                                       delete context.params.query.testInstanceId;\r
+                               }\r
+                               return context;\r
+                       }\r
+               ],\r
+               get: [],\r
+               create: [\r
+                       permissions('jobs'),\r
+                       (context) => { console.log("AFTER PERMISSIONS")},\r
+                       canExecute(), \r
+                       async (context) => {\r
+                               const fullUrl = 'https://localhost/' + context.app.get('base-path') + 'schedule-test';\r
+\r
+                               context.data.executorId = context.params.user._id;\r
+\r
+                               await request({\r
+                                       method: 'post',\r
+                                       url: fullUrl,\r
+                                       body: JSON.stringify(context.data),\r
+                                       headers: {\r
+                                               'Content-Type': 'application/json',\r
+                                               'Authorization': 'Basic ' +\r
+                                                       util.base64Encode(\r
+                                                               context.app.get('serviceApi').aafId + ':' +\r
+                                                               context.app.get('serviceApi').aafPassword)\r
+                                       },\r
+                                       rejectUnauthorized: false\r
+                               }, function (err, res, body) {\r
+                                       if (err) {\r
+                                               logger.error(err);\r
+                                       }\r
+\r
+                                       if (body) {\r
+                                               context.result = JSON.parse(body);\r
+                                       }\r
+\r
+                               });\r
+\r
+                               return context;\r
+                       }\r
+               ],\r
+               update: [],\r
+               patch: [],\r
+               remove: [\r
+                       permissions('jobs'),\r
+                       canExecute(),\r
+                       async function (context) {\r
+                       const fullUrl = 'https://localhost/' + context.app.get('base-path') + 'cancel-test';\r
+\r
+                       if (context.id == null || context.params.user._id == null ||\r
+                               utils.isValidObjectId(context.id) || utils.isValidObjectId(context.params.user._id)) {\r
+                               context.result = {\r
+                                       status: 400,\r
+                                       message: 'Request is invalid.'\r
+                               };\r
+                       }\r
+\r
+                       const postData = {\r
+                               jobId: context.id,\r
+                               executorId: context.params.user._id\r
+                       };\r
+\r
+                       // console.log(JSON.stringify(postData));\r
+\r
+                       await request({\r
+                               method: 'post',\r
+                               url: fullUrl,\r
+                               body: JSON.stringify(postData),\r
+                               headers: {\r
+                                       'Content-Type': 'application/json',\r
+                                       'Authorization': 'Basic ' +\r
+                                               util.base64Encode(\r
+                                                       context.app.get('serviceApi').aafId + ':' +\r
+                                                       context.app.get('serviceApi').aafPassword)\r
+                               },\r
+                               rejectUnauthorized: false\r
+                       }, function (err, res, body) {\r
+                               if (err) {\r
+                                       logger.error(err);\r
+                               }\r
+\r
+                               context.result = JSON.parse(body);\r
+                       });\r
+\r
+                       return context;\r
+               }]\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [agendaJobPopulate()],\r
+               get: [agendaJobPopulate()],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/jobs/jobs.service.js b/otf-frontend/server/src/feathers/services/jobs/jobs.service.js
new file mode 100644 (file)
index 0000000..b444ed4
--- /dev/null
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `groups` service on path `/groups`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/jobs.model');\r
+const hooks = require('./jobs.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               app,\r
+               paginate\r
+       };\r
+\r
+       app.use(app.get('base-path') + 'jobs', createService(options));\r
+\r
+       const service = app.service(app.get('base-path') + 'jobs');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/mailer/mailer.class.js b/otf-frontend/server/src/feathers/services/mailer/mailer.class.js
new file mode 100644 (file)
index 0000000..52d9ee8
--- /dev/null
@@ -0,0 +1,75 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const axios = require('axios');\r
+const Response = require('http-response-object');\r
+const logger = require('../../../lib/logger');\r
+const util = require('../../../lib/otf-util');\r
+const beautify = require('json-beautify');\r
+const sendmail = require('sendmail')();\r
+\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       // function getLink(type, hash) {\r
+       //      const url = 'http://localhost:443/' + type + '?token=' + hash\r
+       //      return url\r
+       // }\r
+\r
+       async find (params) {\r
+               return [];\r
+       }\r
+\r
+       // Check process definition key to see if unique\r
+       async get (id, params) {\r
+\r
+       }\r
+\r
+       async create (data, params) {\r
+\r
+               //send initial email for verification\r
+               //add token to user in database\r
+               sendmail(data, function(err, reply) {\r
+             console.log(err && err.stack);\r
+           });\r
+\r
+\r
+       }\r
+\r
+       async update (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove (id, params) {\r
+               return { id };\r
+       }\r
+\r
+       async parseAndUpload (data, params, method) {\r
+\r
+       }\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/mailer/mailer.hooks.js b/otf-frontend/server/src/feathers/services/mailer/mailer.hooks.js
new file mode 100644 (file)
index 0000000..e7aed9d
--- /dev/null
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { disallow } = require('feathers-hooks-common');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [ disallow('external')],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/mailer/mailer.service.js b/otf-frontend/server/src/feathers/services/mailer/mailer.service.js
new file mode 100644 (file)
index 0000000..62de2e6
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `bpmnValidate` service on path `/bpmn-validate`\r
+const createService = require('./mailer.class.js');\r
+const hooks = require('./mailer.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'mailer', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'mailer');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/messages/messages.class.js b/otf-frontend/server/src/feathers/services/messages/messages.class.js
new file mode 100644 (file)
index 0000000..2076ba9
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const request = require('request');\r
+const Response = require('http-response-object');\r
+const logger = require('../../../lib/logger');\r
+const util = require('../../../lib/otf-util');\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       async find (params) {\r
+               return [];\r
+    }\r
+    \r
+       async get (id, params) {\r
+\r
+       }\r
+\r
+       async create (data, params) {\r
+        return data;\r
+       }   \r
+\r
+       async update (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove (id, params) {\r
+               \r
+       }\r
+\r
+\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/messages/messages.hooks.js b/otf-frontend/server/src/feathers/services/messages/messages.hooks.js
new file mode 100644 (file)
index 0000000..c82ac39
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const logger = require('../../../lib/logger');\r
+const request = require('request-promise');\r
+const agendaJobPopulate = require('../../hooks/agendaJobPopulate');\r
+const utils = require('../../../lib/otf-util');\r
+const axios = require('axios');\r
+const util = require('../../../lib/otf-util');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [\r
+                       \r
+               ],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/messages/messages.service.js b/otf-frontend/server/src/feathers/services/messages/messages.service.js
new file mode 100644 (file)
index 0000000..75a221c
--- /dev/null
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `testInstances` service on path `/test-instances`\r
+const createService = require('./messages.class');\r
+const hooks = require('./messages.hooks');\r
+const io = require('socket.io-client');\r
+const socket = io();\r
\r
+module.exports = function (app) {\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'messages', createService());\r
+\r
+       // Get our initialized service so that we can register hooks\r
+    const service = app.service(app.get('base-path') + 'messages');\r
+    service.on('created', data => {\r
+        app.io.sockets.emit('message', 'service')\r
+    })\r
+\r
+    \r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-definitions/test-definitions.hooks.js b/otf-frontend/server/src/feathers/services/test-definitions/test-definitions.hooks.js
new file mode 100644 (file)
index 0000000..9e33eb2
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const {permissions, limitFields } = require('../../hooks/permissions/permissions.js');\r
+const convertToYAML = require('../../hooks/convertToYAML.js');\r
+const convertJY = require('../../hooks/convertToYAMLRecursive');\r
+const deleteVersion = require('../../hooks/delete-version.js');\r
+const deleteDefinition = require('../../hooks/delete-definition.js');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt'), permissions('testDefinitions')],\r
+               find: [],\r
+               get: [],\r
+               create: [\r
+                       function (context) {\r
+                               context.data.creatorId = context.params.user._id;\r
+                               return context;\r
+                       },\r
+                       convertJY('json')\r
+               ],\r
+               update: [convertJY('json')],\r
+               patch: [deleteVersion(), convertJY('json')],\r
+               remove: [\r
+                       deleteDefinition()\r
+               ]\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [convertToYAML()],\r
+               get: [convertToYAML()],\r
+               create: [convertToYAML()],\r
+               update: [convertToYAML()],\r
+               patch: [convertToYAML()],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-definitions/test-definitions.service.js b/otf-frontend/server/src/feathers/services/test-definitions/test-definitions.service.js
new file mode 100644 (file)
index 0000000..31b8b0a
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `testDefinition` service on path `/test-definition`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/test-definitions.model');\r
+const hooks = require('./test-definitions.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               paginate\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'test-definitions', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'test-definitions');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.class.js b/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.class.js
new file mode 100644 (file)
index 0000000..08a55ee
--- /dev/null
@@ -0,0 +1,109 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const axios = require('axios');\r
+const { parseString, Builder } = require('xml2js');\r
+const pickleRick = require('pickle-rick');\r
+const Response = require('http-response-object');\r
+const request = require('request');\r
+const util = require('../../../lib/otf-util');\r
+\r
+/* eslint-disable no-unused-vars */\r
+class Service {\r
+       constructor (options) {\r
+               this.options = options || {};\r
+       }\r
+\r
+       async find (params) {\r
+\r
+       }\r
+\r
+       // Check process definition key to see if unique\r
+       async get (id, params) {\r
+               // return await axios.get(this.options.app.get('camundaApi').url + 'otf/tcu/process-instance-completion-check/v1/' + id,\r
+               //      {\r
+               //              headers: {\r
+               //                      Authorization: 'Basic ' +\r
+               //              util.base64Encode(\r
+               //                      this.options.app.get('serviceApi').aafId + ':' +\r
+               //                      this.options.app.get('serviceApi').aafPassword)\r
+               //              }\r
+               //      })\r
+               //      .then(result => {\r
+               //              return new Response(200, {}, result.data);\r
+               //      })\r
+               //      .catch(err => {\r
+               //              console.log(err);\r
+               //      });\r
+               \r
+               let options = {\r
+                       url: this.options.app.get('camundaApi').url + 'otf/tcu/process-instance-completion-check/v1/' + id,\r
+                       headers: {\r
+                               'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),\r
+                               'Content-Type': "application/json"\r
+                       },\r
+                       rejectUnauthorized: false\r
+               }\r
+               \r
+               return await new Promise((resolve, reject) => {\r
+                       request.get(options, (err, res, body) => {\r
+                               if(err){\r
+                                       reject(err);\r
+                               }\r
+                               if(res && res.body){\r
+                                       res.body = JSON.parse(res.body);\r
+                               }\r
+                               resolve(res);\r
+                       });\r
+               }).then(\r
+                       res => {\r
+                               return res;\r
+                       }\r
+               ).catch(\r
+                       err => {\r
+                               console.log(err);\r
+                       }\r
+               );\r
+       }\r
+\r
+       async create (data, params) {\r
+               if (Array.isArray(data)) {\r
+                       return Promise.all(data.map(current => this.create(current, params)));\r
+               }\r
+       }\r
+\r
+       async update (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async patch (id, data, params) {\r
+               return data;\r
+       }\r
+\r
+       async remove (id, params) {\r
+               return { id };\r
+       }\r
+\r
+       async parseAndUpload (data, params, method) {\r
+\r
+       }\r
+}\r
+\r
+module.exports = function (options) {\r
+       return new Service(options);\r
+};\r
+\r
+module.exports.Service = Service;\r
diff --git a/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.hooks.js b/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.hooks.js
new file mode 100644 (file)
index 0000000..98dc42e
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt')],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.service.js b/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.service.js
new file mode 100644 (file)
index 0000000..4c022f4
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `bpmnValidate` service on path `/test-definition/image`\r
+const createService = require('./test-execution-status.class.js');\r
+const hooks = require('./test-execution-status.hooks');\r
+\r
+module.exports = function (app) {\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               paginate,\r
+               app\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'test-execution-status', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'test-execution-status');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-executions/test-executions.hooks.js b/otf-frontend/server/src/feathers/services/test-executions/test-executions.hooks.js
new file mode 100644 (file)
index 0000000..5e43d29
--- /dev/null
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { permissions, limitFields } = require('../../hooks/permissions/permissions.js');\r
+const { iff } = require('feathers-hooks-common');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt'), permissions('testExecutions')],\r
+               find: [],\r
+               get: [],\r
+               create: [\r
+                       function (context) {\r
+                               context.data.executorId = context.params.user._id;\r
+                               return context;\r
+                       }\r
+               ],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       after: {\r
+               all: [iff(context => context.params.provider, limitFields())],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-executions/test-executions.service.js b/otf-frontend/server/src/feathers/services/test-executions/test-executions.service.js
new file mode 100644 (file)
index 0000000..30fd7c4
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `testDefinition` service on path `/test-definition`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/test-executions.model');\r
+const hooks = require('./test-executions.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               paginate\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'test-executions', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'test-executions');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-heads/test-heads.hooks.js b/otf-frontend/server/src/feathers/services/test-heads/test-heads.hooks.js
new file mode 100644 (file)
index 0000000..de285e6
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { permissions, limitFields } = require('../../hooks/permissions/permissions.js');\r
+const { iff } = require('feathers-hooks-common');\r
+const convertToJSON = require('../../hooks/convertToJSON.js');\r
+const convertToYAML = require('../../hooks/convertToYAML.js');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt'), permissions('testHeads')],\r
+               find: [],\r
+               get: [],\r
+               create: [convertToJSON(), function (context) {\r
+                       context.data.creatorId = context.params.user._id;\r
+                       return context;\r
+               }\r
+               ],\r
+               update: [convertToJSON()],\r
+               patch: [convertToJSON()],\r
+               remove: [\r
+               ]\r
+       },\r
+\r
+       after: {\r
+               all: [iff(context => context.params.provider, limitFields())],\r
+               find: [convertToYAML()],\r
+               get: [convertToYAML()],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-heads/test-heads.service.js b/otf-frontend/server/src/feathers/services/test-heads/test-heads.service.js
new file mode 100644 (file)
index 0000000..9b3b167
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `testHeads` service on path `/test-heads`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/test-heads.model');\r
+const hooks = require('./test-heads.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               paginate\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'test-heads', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'test-heads');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-instances/test-instances.hooks.js b/otf-frontend/server/src/feathers/services/test-instances/test-instances.hooks.js
new file mode 100644 (file)
index 0000000..ade8335
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const { iff } = require('feathers-hooks-common');\r
+const { permissions, limitFields } = require('../../hooks/permissions/permissions');\r
+const convertToJSON = require('../../hooks/convertToJSON.js');\r
+const convertToYAML = require('../../hooks/convertToYAML.js');\r
+const testDefinitionIsDeployed = require('../../hooks/testDefinitionIsDeployed.js');\r
+const {skipRemainingHooks} = require('feathers-hooks-common');\r
+\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [authenticate('jwt'), permissions('testInstances')],\r
+               find: [],\r
+               get: [],\r
+               create: [iff(testDefinitionIsDeployed(), convertToJSON()).else(function(context) {\r
+                       \r
+                       throw new Error('Test Definition Must have a deployed BPMN.');\r
+                       \r
+                 }\r
+        )],\r
+               update: [convertToJSON()],\r
+               patch: [],\r
+               remove: [\r
+               ]\r
+       },\r
+\r
+       after: {\r
+               all: [],\r
+               find: [convertToYAML()],\r
+               get: [ convertToYAML()],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/test-instances/test-instances.service.js b/otf-frontend/server/src/feathers/services/test-instances/test-instances.service.js
new file mode 100644 (file)
index 0000000..bb74ef6
--- /dev/null
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `testInstances` service on path `/test-instances`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/test-instances.model');\r
+const hooks = require('./test-instances.hooks');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               paginate\r
+       };\r
+\r
+       // Initialize our service with any options it requires\r
+       app.use(app.get('base-path') + 'test-instances', createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'test-instances');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/users/users.hooks.js b/otf-frontend/server/src/feathers/services/users/users.hooks.js
new file mode 100644 (file)
index 0000000..d20655b
--- /dev/null
@@ -0,0 +1,181 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { authenticate } = require('@feathersjs/authentication').hooks;\r
+const filter = require('../../hooks/filters.js');\r
+const checkPermissions = require('feathers-permissions');\r
+const authManagement = require('../auth-management/notifier');\r
+const errors = require('@feathersjs/errors');\r
+\r
+const {\r
+       hashPassword, protect\r
+} = require('@feathersjs/authentication-local').hooks;\r
+const { iff, disallow } = require('feathers-hooks-common'); \r
+const verifyHooks = require('feathers-authentication-management').hooks;\r
+const skip = require('@feathersjs/feathers').SKIP;\r
+\r
+const { permissions, limitFields } = require('../../hooks/permissions/permissions');\r
+\r
+module.exports = {\r
+       before: {\r
+               all: [],\r
+               find: [\r
+                       authenticate('jwt'),\r
+                       permissions('users'),\r
+                       function(context){\r
+                               if(!context.params.user){\r
+                                       return skip;\r
+                               }\r
+                       }\r
+               ],\r
+               get: [\r
+                       authenticate('jwt'),\r
+                       permissions('users'),\r
+                       function(context){\r
+                               if(!context.params.user){\r
+                                       return skip;\r
+                               }\r
+                       }\r
+               ],\r
+               create: [hashPassword(),\r
+                               function(context){\r
+                                       return verifyHooks.addVerification(context.app.get('base-path') + 'authManagement')(context);\r
+                               },\r
+                               function (context) {\r
+                                       context.data.enabled = false;\r
+                                       // await context.app.services[context.app.get('base-path') + 'groups']\r
+                                       // .find({\r
+                                       //      query : {\r
+                                       //              groupName: "Public"\r
+                                       //      }\r
+                                       // })\r
+                                       // .then( result => {   \r
+                                       //      if(result){\r
+                                       //              await context.app.services[context.app.get('base-path') + 'groups']\r
+                                       //              .patch({\r
+                                       //                      _id : result._id,\r
+                    //                 $push: { members: { userId : user._id, roles: ["user"]}}\r
+                                       //              });\r
+                                       //      }\r
+                                       // });\r
+                                       context.data.groups = [\r
+                                               {\r
+                                                       groupId: '5bdb2bdbd6b0d1f97953fbd7',\r
+                                                       permissions: [\r
+                                                               'admin'\r
+                                                       ]\r
+                                               }\r
+                                       ];\r
+\r
+                               }\r
+               ],\r
+               update: [\r
+                       hashPassword(),\r
+                       authenticate('jwt'),\r
+                       permissions('users')\r
+               ],\r
+               patch:\r
+                       [\r
+\r
+                               hashPassword(),\r
+                               authenticate('jwt'),\r
+                               iff(context => context.params.provider === undefined).else(\r
+                                       permissions('users'),\r
+                                       async function(context){\r
+                                               if(context.data.enabled){\r
+                                                        await this.get(context.id)\r
+                                                               .then(function(user) {\r
+                                                                       if(!user.enabled){\r
+                                                                               context.sendEmail = true;\r
+\r
+                                                                       }\r
+                                                               });\r
+                                               }\r
+                                       }\r
+                               )\r
+                       // commonHooks\r
+                       //      .iff(checkPermissions({\r
+                       //              roles: [ 'admin' ]\r
+                       //      }))\r
+                       //      .else(commonHooks.iff(\r
+                       //              commonHooks.isProvider('external'),\r
+                       //              commonHooks.preventChanges(\r
+                       //                      'email',\r
+                       //                      'isVerified',\r
+                       //                      'verifyToken',\r
+                       //                      'verifyShortToken',\r
+                       //                      'verifyExpires',\r
+                       //                      'verifyChanges',\r
+                       //                      'resetToken',\r
+                       //                      'resetShortToken',\r
+                       //                      'resetExpires'\r
+                       //              )\r
+                       //      ))\r
+               ],\r
+               remove: [\r
+                       authenticate('jwt'),\r
+                       permissions('users')\r
+               ]\r
+       },\r
+\r
+       after: {\r
+               all: [\r
+                       // Make sure the password field is never sent to the client\r
+                       // Always must be the last hook\r
+                       protect('password'),\r
+               ],\r
+               find: [iff(context => context.params.provider === undefined).else(limitFields())],\r
+               get: [iff(context => context.params.provider === undefined).else(limitFields())],\r
+               create: [\r
+                       context => {\r
+                               authManagement(context.app).notifier('resendVerifySignup', context.result);\r
+                       },\r
+                       function (context) {\r
+                               \r
+                               // await context.app.services[context.app.get('base-path') + 'groups']\r
+                               // .get(context.data.parentGroupId, context.params)\r
+                               // .then( result => {   \r
+                               //      group = result;\r
+                               // });\r
+                       },\r
+                       verifyHooks.removeVerification()\r
+               ],\r
+               update: [iff(context => context.params.provider === undefined).else(limitFields())],\r
+               patch: [iff(context => context.params.provider === undefined).else(limitFields()),\r
+                       context => {\r
+                               let data = context['data']\r
+                               if(data && context.sendEmail){\r
+                                       let enabled = data['enabled'];\r
+                                               if(enabled){\r
+                                                       authManagement(context.app).notifier('sendApprovalNotification', context.result)\r
+\r
+                                               }\r
+                               }\r
+                       }\r
+               ],\r
+               remove: [iff(context => context.params.provider === undefined).else(limitFields())]\r
+       },\r
+\r
+       error: {\r
+               all: [],\r
+               find: [],\r
+               get: [],\r
+               create: [],\r
+               update: [],\r
+               patch: [],\r
+               remove: []\r
+       }\r
+};\r
diff --git a/otf-frontend/server/src/feathers/services/users/users.service.js b/otf-frontend/server/src/feathers/services/users/users.service.js
new file mode 100644 (file)
index 0000000..b4350bb
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+// Initializes the `users` service on path `/users`\r
+const createService = require('feathers-mongoose');\r
+const createModel = require('../../models/users.model');\r
+const hooks = require('./users.hooks');\r
+\r
+const RateLimit = require('express-rate-limit');\r
+const MongoStore = require('rate-limit-mongo');\r
+\r
+module.exports = function (app) {\r
+       const Model = createModel(app);\r
+       const paginate = app.get('paginate');\r
+\r
+       const options = {\r
+               Model,\r
+               paginate\r
+       };\r
+\r
+       const mongoConfig = app.get('mongo');\r
+       const rateLimitConfig = app.get('rate-limit');\r
+\r
+       const createUserLimiter = new RateLimit({\r
+               store: new MongoStore({\r
+                       uri: 'mongodb://' + mongoConfig.username + ':' + mongoConfig.password + '@' + mongoConfig.baseUrl +\r
+                               mongoConfig.dbOtf + '?replicaSet=' + mongoConfig.replicaSet,\r
+                       collectionName: rateLimitConfig.mongoStore.collection\r
+               }),\r
+               max: app.get('rate-limit').services.users.max,\r
+               windowsMs: app.get('rate-limit').services.users.windowMs,\r
+               message: app.get('rate-limit').services.users.message\r
+       });\r
+\r
+       // Initialize our service with any options it requires,\r
+       // and limit any POST methods.\r
+       app.use(app.get('base-path') + 'users', (req, res, next) => {\r
+               if (req.method === 'POST') {\r
+                       createUserLimiter(req, res, next);\r
+               } else {\r
+                       next();\r
+               }\r
+       }, createService(options));\r
+\r
+       // Get our initialized service so that we can register hooks\r
+       const service = app.service(app.get('base-path') + 'users');\r
+\r
+       service.hooks(hooks);\r
+};\r
diff --git a/otf-frontend/server/src/lib/logger.js b/otf-frontend/server/src/lib/logger.js
new file mode 100644 (file)
index 0000000..4927948
--- /dev/null
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const { createLogger, format, transports, addColors } = require('winston');\r
+const { combine, timestamp, label, simple, colorize, splat, printf } = format;\r
+const cluster = require('cluster');\r
+\r
+const getLabel = function () {\r
+       if (cluster.isMaster) return 'OTF-master';\r
+       return 'OTF-worker-' + cluster.worker.id;\r
+};\r
+\r
+const config = {\r
+       levels: {\r
+               error: 0,\r
+               debug: 1,\r
+               warn: 2,\r
+               data: 3,\r
+               info: 4,\r
+               verbose: 5,\r
+               silly: 6,\r
+               custom: 7\r
+       },\r
+       colors: {\r
+               error: 'red',\r
+               debug: 'blue',\r
+               warn: 'yellow',\r
+               data: 'grey',\r
+               info: 'green',\r
+               verbose: 'cyan',\r
+               silly: 'magenta',\r
+               custom: 'yellow'\r
+       }\r
+};\r
+\r
+addColors(config.colors);\r
+\r
+const logFormat = printf(info => {\r
+       return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`;\r
+});\r
+\r
+const logger = module.exports = createLogger({\r
+       levels: config.levels,\r
+       format: combine(\r
+               label({ label: getLabel() }),\r
+               timestamp(),\r
+               splat(),\r
+               colorize(),\r
+               simple(),\r
+               logFormat\r
+       ),\r
+       transports: [\r
+               new transports.Console()\r
+       ],\r
+       level: 'custom'\r
+});\r
+\r
+module.exports = logger;\r
diff --git a/otf-frontend/server/src/lib/mongoose.js b/otf-frontend/server/src/lib/mongoose.js
new file mode 100644 (file)
index 0000000..a8d5b9d
--- /dev/null
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const mongoose = require('mongoose');\r
+\r
+module.exports = function (app) {\r
+       const mongoData = app.get('mongo');\r
+       const connectionString = 'mongodb://' + mongoData.username + ':' + mongoData.password + '@' + mongoData.baseUrl + mongoData.dbOtf + '?replicaSet=' + mongoData.replicaSet;\r
+\r
+       mongoose.connect(connectionString, { useNewUrlParser: true }).then(null, error => {\r
+               console.log('caught', error.message);\r
+       });\r
+       mongoose.Promise = global.Promise;\r
+       mongoose.set('useCreateIndex', true);\r
+\r
+       app.set('mongooseClient', mongoose);\r
+};\r
diff --git a/otf-frontend/server/src/lib/music.js b/otf-frontend/server/src/lib/music.js
new file mode 100644 (file)
index 0000000..aa39cdc
--- /dev/null
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const config = require('../config/default.json');\r
+const btoa = require('btoa');\r
+\r
+module.exports.parser = function (response) {\r
+       var jsonObject = {\r
+               data: [],\r
+               status: ''\r
+       };\r
+\r
+       if (response.result) {\r
+               // loop through musics rusults and make it into an array\r
+               for (var key in response.result) {\r
+                       if (response.result.hasOwnProperty(key)) {\r
+                               jsonObject.data.push(response.result[key]);\r
+                       }\r
+               }\r
+       }\r
+\r
+       // set store status in new reponse\r
+       jsonObject.status = response.status;\r
+\r
+       return jsonObject;\r
+};\r
+\r
+module.exports.stringifyParams = function (params) {\r
+       var string = '';\r
+       var count = 0;\r
+\r
+       for (var key in params.query) {\r
+               if (params.query.hasOwnProperty(key) && key[0] != '$') {\r
+                       if (count > 0) {\r
+                               string += '&&';\r
+                       }\r
+                       string += key + '=' + params.query[key];\r
+                       count++;\r
+               }\r
+       }\r
+\r
+       return string;\r
+};\r
+\r
+module.exports.musicHeaders = {\r
+       'Content-Type': 'application/json',\r
+       'ns': config.music.ns,\r
+       'X-minorVersion': config.music['X-minorVersion'],\r
+       'X-patchVersion': config.music['X-patchVersion'],\r
+       'Authorization': 'Basic ' + btoa(config.music.username + ':' + config.music.password)\r
+};\r
+\r
+module.exports.musicUrl = config.music.url;\r
diff --git a/otf-frontend/server/src/lib/otf-util.js b/otf-frontend/server/src/lib/otf-util.js
new file mode 100644 (file)
index 0000000..359a4d1
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+const ObjectId = require('mongoose').Types.ObjectId;\r
+\r
+module.exports = {\r
+       base64Encode: function (value) {\r
+               return Buffer.from(value).toString('base64');\r
+       },\r
+       isValidObjectId: function (value) {\r
+               const generatedObjectId = new ObjectId(value);\r
+\r
+               return generatedObjectId.toString() === value;\r
+       },\r
+       isJsonString: function (str) {\r
+               try {\r
+                       JSON.parse(str);\r
+               } catch (e) {\r
+                       return false;\r
+               }\r
+               return true;\r
+       }\r
+};\r
diff --git a/otf-frontend/server/test/app.test.js b/otf-frontend/server/test/app.test.js
new file mode 100644 (file)
index 0000000..67338c3
--- /dev/null
@@ -0,0 +1,55 @@
+const assert = require('assert');\r
+const rp = require('request-promise');\r
+const url = require('url');\r
+const app = require('../src/app');\r
+\r
+const port = app.get('port') || 3030;\r
+const getUrl = pathname => url.format({\r
+       hostname: app.get('host') || 'localhost',\r
+       protocol: 'http',\r
+       port,\r
+       pathname\r
+});\r
+\r
+describe('Feathers application tests', () => {\r
+       before(function (done) {\r
+               this.server = app.listen(port);\r
+               this.server.once('listening', () => done());\r
+       });\r
+\r
+       after(function (done) {\r
+               this.server.close(done);\r
+       });\r
+\r
+       it('starts and shows the index page', () => {\r
+               return rp(getUrl()).then(body =>\r
+                       assert.ok(body.indexOf('<html>') !== -1)\r
+               );\r
+       });\r
+\r
+       describe('404', function () {\r
+               it('shows a 404 HTML page', () => {\r
+                       return rp({\r
+                               url: getUrl('path/to/nowhere'),\r
+                               headers: {\r
+                                       'Accept': 'text/html'\r
+                               }\r
+                       }).catch(res => {\r
+                               assert.strictEqual(res.statusCode, 404);\r
+                               assert.ok(res.error.indexOf('<html>') !== -1);\r
+                       });\r
+               });\r
+\r
+               it('shows a 404 JSON error without stack trace', () => {\r
+                       return rp({\r
+                               url: getUrl('path/to/nowhere'),\r
+                               json: true\r
+                       }).catch(res => {\r
+                               assert.strictEqual(res.statusCode, 404);\r
+                               assert.strictEqual(res.error.code, 404);\r
+                               assert.strictEqual(res.error.message, 'Page not found');\r
+                               assert.strictEqual(res.error.name, 'NotFound');\r
+                       });\r
+               });\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/hooks/group-filter.test.js b/otf-frontend/server/test/hooks/group-filter.test.js
new file mode 100644 (file)
index 0000000..71d02c6
--- /dev/null
@@ -0,0 +1,25 @@
+const assert = require('assert');\r
+const feathers = require('@feathersjs/feathers');\r
+const groupFilter = require('../../src/hooks/group-filter');\r
+\r
+describe('\'groupFilter\' hook', () => {\r
+       let app;\r
+\r
+       beforeEach(() => {\r
+               app = feathers();\r
+\r
+               app.use('/dummy', {\r
+                       async get (id) {\r
+                               return { id };\r
+                       }\r
+               });\r
+\r
+               app.service('dummy').hooks({});\r
+       });\r
+\r
+       it('runs the hook', async () => {\r
+               const result = await app.service('dummy').get('test');\r
+\r
+               assert.deepEqual(result, { id: 'test' });\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/bpmn-upload.test.js b/otf-frontend/server/test/services/bpmn-upload.test.js
new file mode 100644 (file)
index 0000000..23d1ff0
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'bpmnUpload\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('bpmn-upload');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/bpmn-validate.test.js b/otf-frontend/server/test/services/bpmn-validate.test.js
new file mode 100644 (file)
index 0000000..fb00767
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'bpmnValidate\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('bpmn-validate');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/groups-m.test.js b/otf-frontend/server/test/services/groups-m.test.js
new file mode 100644 (file)
index 0000000..6e46510
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'groupsM\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('groups-m');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/groups.test.js b/otf-frontend/server/test/services/groups.test.js
new file mode 100644 (file)
index 0000000..c66c9c7
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'groups\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('groups');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/health.test.js b/otf-frontend/server/test/services/health.test.js
new file mode 100644 (file)
index 0000000..212f034
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'health\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('health');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/strategy-upload.test.js b/otf-frontend/server/test/services/strategy-upload.test.js
new file mode 100644 (file)
index 0000000..034e933
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'strategyUpload\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('strategy-upload');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/test-definition.test.js b/otf-frontend/server/test/services/test-definition.test.js
new file mode 100644 (file)
index 0000000..556d710
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'testDefinition\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('test-definition');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/test-heads.test.js b/otf-frontend/server/test/services/test-heads.test.js
new file mode 100644 (file)
index 0000000..44fa679
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'testHeads\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('test-heads');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/test-instances.test.js b/otf-frontend/server/test/services/test-instances.test.js
new file mode 100644 (file)
index 0000000..2db66b9
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'testInstances\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('test-instances');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/test-requests.test.js b/otf-frontend/server/test/services/test-requests.test.js
new file mode 100644 (file)
index 0000000..51304e5
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'testRequests\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('test-requests');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/test-strategies.test.js b/otf-frontend/server/test/services/test-strategies.test.js
new file mode 100644 (file)
index 0000000..35bf0d5
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'testStrategies\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('test-strategies');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/tests.test.js b/otf-frontend/server/test/services/tests.test.js
new file mode 100644 (file)
index 0000000..0d03d13
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'tests\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('tests');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
diff --git a/otf-frontend/server/test/services/users.test.js b/otf-frontend/server/test/services/users.test.js
new file mode 100644 (file)
index 0000000..bed24e0
--- /dev/null
@@ -0,0 +1,10 @@
+const assert = require('assert');\r
+const app = require('../../src/app');\r
+\r
+describe('\'users\' service', () => {\r
+       it('registered the service', () => {\r
+               const service = app.service('users');\r
+\r
+               assert.ok(service, 'Registered the service');\r
+       });\r
+});\r
index 0ebb8f8..182eaa4 100644 (file)
@@ -1,9 +1,14 @@
 FROM python:2.7\r
 \r
-ARG HTTP_PROXY="localhost:8080"\r
-ARG HTTPS_PROXY="localhost:8080"\r
-ARG http_proxy="localhost:8080"\r
-ARG https_proxy="localhost:8080"\r
+# ARG HTTP_PROXY="localhost:8080"\r
+# ARG HTTPS_PROXY="localhost:8080"\r
+# ARG http_proxy="localhost:8080"\r
+# ARG https_proxy="localhost:8080"\r
+\r
+\r
+ENV NAMESPACE=namespace\r
+ENV APP_NAME=otf-ping-test-head\r
+ENV APP_VERSION=1.0\r
 \r
 RUN python --version\r
 \r
index 7358b21..49267ac 100644 (file)
@@ -1,9 +1,18 @@
 FROM python:2.7\r
 \r
-ARG HTTP_PROXY="http://localhost:8080" \r
-ARG HTTPS_PROXY="http://localhost:8080" \r
-ARG http_proxy="http://localhost:8080" \r
-ARG https_proxy="http://localhost:8080"\r
+# ARG HTTP_PROXY="http://localhost:8080" \r
+# ARG HTTPS_PROXY="http://localhost:8080" \r
+# ARG http_proxy="http://localhost:8080" \r
+# ARG https_proxy="http://localhost:8080"\r
+\r
+ENV NAMESPACE=namespace\r
+ENV APP_NAME=otf-robot-test-head\r
+ENV APP_VERSION=1.0\r
+ENV OTF_MONGO_HOSTS=localhost:27017\r
+ENV OTF_MONGO_DATABASE=otf\r
+ENV OTF_MONGO_REPLICASET=mongoOTF\r
+ENV OTF_MONGO_USERNAME=username\r
+ENV OTF_MONGO_PASSWORD=password\r
 \r
 RUN python --version\r
 \r
diff --git a/otf-service-api/.gitignore b/otf-service-api/.gitignore
new file mode 100644 (file)
index 0000000..681073c
--- /dev/null
@@ -0,0 +1,34 @@
+/target/\r
+tokens/\r
+out/\r
+src/main/resources/otf_dev.p12\r
+/otf/\r
+\r
+\r
+*.log\r
+\r
+!.mvn/wrapper/maven-wrapper.jar\r
+\r
+### STS ###\r
+.apt_generated\r
+.classpath\r
+.factorypath\r
+.project\r
+.settings\r
+.springBeans\r
+.sts4-cache\r
+\r
+### IntelliJ IDEA ###\r
+.idea\r
+*.iws\r
+*.iml\r
+*.ipr\r
+/null/\r
+\r
+### NetBeans ###\r
+/nbproject/private/\r
+/build/\r
+/nbbuild/\r
+/dist/\r
+/nbdist/\r
+/.nb-gradle/\r
diff --git a/otf-service-api/Jenkinsfile b/otf-service-api/Jenkinsfile
new file mode 100644 (file)
index 0000000..68e8d66
--- /dev/null
@@ -0,0 +1,169 @@
+#!/usr/bin/env groovy\r
+\r
+properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "username"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_CAMUNDA_DB', defaultValue: "otf_camunda_dev_db"],\r
+        [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-oran-otf"]\r
+        \r
+]]])\r
+\r
+echo "Build branch: ${env.BRANCH_NAME}"\r
+\r
+node("docker") {\r
+    stage 'Checkout'\r
+    checkout scm\r
+    PHASES = PHASE.tokenize('_')\r
+    echo "PHASES : " + PHASES\r
+    pom = readMavenPom file: 'pom.xml'\r
+    ARTIFACT_ID = pom.artifactId\r
+    VERSION = pom.version\r
+    LABEL_VERSION = pom.version.replaceAll("\\.", "-")\r
+    echo "LabelVerion: " + LABEL_VERSION\r
+    NAMESPACE = pom.groupId\r
+    echo "Tiller Namespace: " + TILLER_NAMESPACE\r
+    DOCKER_REGISTRY = pom.properties['docker.registry']\r
+\r
+\r
+\r
+       if( ENV.equalsIgnoreCase("dev") ){\r
+           IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE  + "/" + ARTIFACT_ID + ":" + VERSION\r
+       \r
+       }\r
+       if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){\r
+           IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID + ":" + VERSION\r
+       \r
+       }    \r
+    if( ENV.equalsIgnoreCase("st") ){\r
+        IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID + ":" + VERSION\r
+    \r
+    } \r
+       \r
+       echo "Artifact: " + IMAGE_NAME\r
+\r
+    withEnv(["PATH=${env.PATH}:${tool 'mvn352'}/bin:${tool 'jdk180'}/bin:${env.WORKSPACE}/linux-amd64", "JAVA_HOME=${tool 'jdk180'}", "MAVEN_HOME=${tool 'mvn352'}", "HELM_HOME=${env.WORKSPACE}"]) {\r
+\r
+        echo "JAVA_HOME=${env.JAVA_HOME}"\r
+        echo "MAVEN_HOME=${env.MAVEN_HOME}"\r
+        echo "PATH=${env.PATH}"\r
+        echo "HELM_HOME=${env.HELM_HOME}"\r
+\r
+        wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [\r
+                [fileId: 'maven-settings.xml', variable: 'MAVEN_SETTINGS']\r
+        ]]) {\r
+\r
+\r
+            if (PHASES.contains("BUILD")) {\r
+                stage 'Compile'\r
+                sh 'mvn -s $MAVEN_SETTINGS clean compile'\r
+\r
+                //stage 'Unit Test'\r
+                sh 'mvn -s $MAVEN_SETTINGS test -DskipTests'\r
+\r
+                stage 'Package'\r
+                sh 'mvn -s $MAVEN_SETTINGS package -DskipTests'\r
+//                sh 'mvn -DskipTests -Dmaven.test.skip=true -s $MAVEN_SETTINGS package'\r
+\r
+//                stage 'Verify'\r
+                sh 'mvn -s $MAVEN_SETTINGS verify -DskipTests'\r
+\r
+                stage 'Publish Artifact'\r
+\r
+                withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {\r
+\r
+                    echo "Artifact: " + IMAGE_NAME\r
+\r
+                    sh """\r
+                                               docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD\r
+                                               docker build -t $IMAGE_NAME -f target/Dockerfile target\r
+                                               docker push $IMAGE_NAME\r
+                                       """\r
+                }\r
+\r
+            }\r
+            if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY")) {\r
+\r
+                stage 'Init Helm'\r
+\r
+                //check if helm exists if not install\r
+                if (fileExists('linux-amd64/helm')) {\r
+                    sh """\r
+                                               echo "helm is already installed"\r
+                                       """\r
+                } else {\r
+                    //download helm\r
+                    sh """\r
+                                               echo "installing helm"\r
+                                               wget  https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz\r
+                                               tar -xf helm-v2.8.2-linux-amd64.tar.gz\r
+                                               rm helm-v2.8.2-linux-amd64.tar.gz\r
+                                       """\r
+                }\r
+\r
+                withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) {\r
+\r
+                    dir('helm') {\r
+                        //check if charts are valid, and then perform dry run, if successful then upgrade/install charts\r
+\r
+                        if (PHASES.contains("UNDEPLOY")) {\r
+                            stage 'Undeploy'\r
+\r
+                            sh """\r
+                                                       helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID\r
+                                               """\r
+                        }\r
+\r
+                        //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace\r
+                        if (PHASES.contains("DEPLOY")) {\r
+                            stage 'Deploy'\r
+                            withCredentials([\r
+                                    usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD'),\r
+                            ]) {\r
+                                sh """                                                                         \r
+                                                                   echo "Validate Yaml"\r
+                                    helm lint $ARTIFACT_ID\r
+\r
+                                    echo "View Helm Templates"\r
+                                    helm template $ARTIFACT_ID \\r
+                                       --set appName=$ARTIFACT_ID \\r
+                                       --set version=$VERSION \\r
+                                       --set image=$IMAGE_NAME\\r
+                                       --set env=$ENV \\r
+                                       --set otf.mongo.username=$USERNAME \\r
+                                       --set otf.mongo.password=$PASSWORD \\r
+                                       --set namespace=$TILLER_NAMESPACE\r
+                                       \r
+\r
+                                    echo "Perform Dry Run Of Install"\r
+                                    helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \\r
+                                       --set appName=$ARTIFACT_ID \\r
+                                       --set version=$VERSION \\r
+                                       --set image=$IMAGE_NAME\\r
+                                       --set env=$ENV \\r
+                                       --set otf.mongo.username=$USERNAME \\r
+                                       --set otf.mongo.password=$PASSWORD \\r
+                                       --set namespace=$TILLER_NAMESPACE\r
+\r
+                                    echo "Helm Install/Upgrade"\r
+                                    helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \\r
+                                       --set appName=$ARTIFACT_ID \\r
+                                       --set version=$VERSION \\r
+                                       --set image=$IMAGE_NAME\\r
+                                       --set env=$ENV \\r
+                                       --set otf.mongo.username=$USERNAME \\r
+                                       --set otf.mongo.password=$PASSWORD \\r
+                                       --set namespace=$TILLER_NAMESPACE\r
+                                    \r
+                                                               """\r
+                            }\r
+                        }\r
+\r
+                    }\r
+                }\r
+            }\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-service-api/LICENSE.txt b/otf-service-api/LICENSE.txt
new file mode 100644 (file)
index 0000000..695ac56
--- /dev/null
@@ -0,0 +1,28 @@
+Unless otherwise specified, all software contained herein is licensed\r
+under the Apache License, Version 2.0 (the "Software License");\r
+you may not use this software except in compliance with the Software\r
+License. You may obtain a copy of the Software License at\r
+\r
+http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+Unless required by applicable law or agreed to in writing, software\r
+distributed under the Software License is distributed on an "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+See the Software License for the specific language governing permissions\r
+and limitations under the Software License.\r
+\r
+\r
+\r
+Unless otherwise specified, all documentation contained herein is licensed\r
+under the Creative Commons License, Attribution 4.0 Intl. (the\r
+"Documentation License"); you may not use this documentation except in\r
+compliance with the Documentation License. You may obtain a copy of the\r
+Documentation License at\r
+\r
+https://creativecommons.org/licenses/by/4.0/\r
+\r
+Unless required by applicable law or agreed to in writing, documentation\r
+distributed under the Documentation License is distributed on an "AS IS"\r
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+implied. See the Documentation License for the specific language governing\r
+permissions and limitations under the Documentation License.\r
diff --git a/otf-service-api/README.txt b/otf-service-api/README.txt
new file mode 100644 (file)
index 0000000..b6e4e99
--- /dev/null
@@ -0,0 +1,13 @@
+You must setup environment variables to run and test locally\r
+These environment variables will be secretes when deployed on kubernetes.\r
+       AAF_ID (mechid for cadi aaf)\r
+       AAF_PASSWORD (password for mechid)\r
+       CADI_KEYFILE (cadi keyfile location for aaf)\r
+               \r
+               Generate AAF_PASSWORD and CADI_KEYFILE:\r
+                       java -jar cadi-core-1.4.2.jar keygen keyfile\r
+                       java -jar cadi-core-1.4.2.jar digest AAF_MECHID_PASSWORD keyfile  > digest.txt 2>&1\r
+       \r
+       AAF_PERM_TYPE (permission type to check for when authorization a user)\r
+       \r
+       
\ No newline at end of file
diff --git a/otf-service-api/docker/Dockerfile b/otf-service-api/docker/Dockerfile
new file mode 100644 (file)
index 0000000..e0e9e53
--- /dev/null
@@ -0,0 +1,43 @@
+FROM openjdk:8\r
+\r
+ENV NAMESPACE=namespace\r
+ENV APP_NAME=otf-service-api\r
+ENV AAF_PERM_TYPE=type\r
+ENV AAF_ID=username\r
+ENV AAF_MECH_PASSWORD=password\r
+ENV AAF_PASSWORD=password\r
+ENV CADI_KEYFILE=/opt/secret/keyfile\r
+ENV CADI_HOSTNAME=localhost\r
+ENV APP_VERSION=1.0\r
+ENV OTF_MONGO_HOSTS=localhost:27017\r
+ENV OTF_MONGO_USERNAME=username\r
+ENV OTF_MONGO_PASSWORD=password\r
+ENV OTF_MONGO_REPLICASET=mongoOTF\r
+ENV OTF_MONGO_DATABASE=otf\r
+ENV otf.camunda.host=https://localhost\r
+ENV otf.camunda.port=31313\r
+ENV otf.camunda.executionUri=otf/tcu/execute-test/v1\r
+ENV otf.camunda.pollingUri=otf/tcu/process-instance-completion-check/v1\r
+ENV otf.camunda.deploymentUri=otf/tcu/deploy-test-strategy-zip/v1\r
+ENV otf.camunda.processDefinitionKeyUri=rest/process-definition/key\r
+ENV otf.camunda.deploymentDeletionUri=otf/tcu/delete-test-strategy/v1/deployment-id\r
+ENV otf.camunda.testDefinitionDeletionUri=otf/tcu/delete-test-strategy/v1/test-definition-id\r
+ENV otf.camunda.uri.execute-test=otf/tcu/execute/workflowRequest\r
+ENV otf.camunda.uri.process-instance-completion-check=otf/tcu/process-instance-completion-check/v1\r
+ENV otf.camunda.uri.deploy-test-strategy-zip=otf/tcu/deploy-test-strategy-zip/v1\r
+ENV otf.camunda.uri.process-definition=rest/process-definition/key\r
+ENV otf.camunda.uri.delete-test-strategy=otf/tcu/delete-test-strategy/v1/deployment-id\r
+ENV otf.camunda.uri.delete-test-strategy-test-definition-id=otf/tcu/delete-test-strategy/v1/test-definition-id\r
+ENV otf.camunda.uri.health=/otf/health/v1\r
+ENV otf.api.poll-interval=6000\r
+ENV otf.api.poll-attempts=50\r
+ENV OTF_CERT_PATH=opt/cert/cert.p12\r
+ENV OTF_CERT_PASS=password\r
+\r
+COPY otf-service-api.jar app.jar\r
+\r
+RUN mkdir -p /otf/logs\r
+\r
+ADD src src\r
+\r
+ENTRYPOINT ["java", "-jar", "app.jar"]\r
diff --git a/otf-service-api/helm/otf-service-api/Chart.yaml b/otf-service-api/helm/otf-service-api/Chart.yaml
new file mode 100644 (file)
index 0000000..7c05894
--- /dev/null
@@ -0,0 +1,5 @@
+apiVersion: v1\r
+appVersion: "1.0"\r
+description: A Helm chart the OTF TCU Service API\r
+name: otf-service-api\r
+version: 0.0.1-SNAPSHOT
\ No newline at end of file
diff --git a/otf-service-api/helm/otf-service-api/templates/deployment.yaml b/otf-service-api/helm/otf-service-api/templates/deployment.yaml
new file mode 100644 (file)
index 0000000..3a406d3
--- /dev/null
@@ -0,0 +1,280 @@
+apiVersion: extensions/v1beta1\r
+kind: Deployment\r
+metadata:\r
+  name: {{ .Values.appName}}\r
+  namespace: {{.Values.namespace}}\r
+  labels:\r
+    app: {{ .Values.appName}}\r
+    version: {{.Values.version}}\r
+spec:\r
+  {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+  replicas: {{ .Values.replicas.prod}}\r
+  {{ else if  eq .Values.env "st"}}\r
+  replicas: {{ .Values.replicas.st}}\r
+  {{ else }}\r
+  replicas: {{ .Values.replicas.dev}}\r
+  {{ end }}\r
+  selector:\r
+    matchLabels:\r
+      app: {{ .Values.appName}}\r
+      version: {{.Values.version}}\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: {{ .Values.appName}}\r
+        version: {{.Values.version}}\r
+    spec:\r
+      revisionHistoryLimit: 1   # keep one replica set to allow rollback\r
+      minReadySeconds: 10\r
+      strategy:\r
+        # indicate which strategy we want for rolling update\r
+        type: RollingUpdate\r
+        rollingUpdate:\r
+          maxSurge: 1\r
+          maxUnavailable: 1\r
+      serviceAccount: default\r
+      volumes:\r
+      - name: {{ .Values.appName}}-aaf-volume\r
+        secret:\r
+          secretName: {{.Values.sharedSecret}}\r
+      - name: {{ .Values.appName}}-keyfile-volume\r
+        secret:\r
+          secretName: {{.Values.sharedSecret}}\r
+          optional: true\r
+          items:\r
+          - key: cadi_keyfile\r
+            path: keyfile\r
+      - name: {{ .Values.appName}}-cert-volume\r
+        secret:\r
+          secretName: {{.Values.sharedCert}}\r
+          optional: true\r
+          items:\r
+          - key: PKCS12_CERT\r
+            {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+            path: {{ .Values.cert.prod.name | quote }}\r
+            {{ else if eq  .Values.env "st" }}\r
+            path: {{ .Values.cert.st.name | quote }}\r
+            {{ else }}\r
+            path: {{ .Values.cert.dev.name | quote }}\r
+            {{ end }}          \r
+      {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}\r
+      {{else}}\r
+      - name: logging-pvc\r
+        persistentVolumeClaim:\r
+          {{if eq .Values.env "prod"}}\r
+          claimName: {{ .Values.pvc.prod | quote }}\r
+          {{ else }}\r
+          claimName: {{ .Values.pvc.dev | quote }}\r
+          {{ end }}\r
+      {{end}}\r
+      containers:\r
+      - name: {{ .Values.appName}}\r
+        image: {{ .Values.image}}\r
+        imagePullPolicy: Always\r
+        ports:\r
+        - name: https\r
+          containerPort: 8443\r
+          nodePort: {{.Values.nodePort}}\r
+          protocol: TCP\r
+        {{ if eq .Values.env "st"}}\r
+        resources:\r
+          limits: \r
+            memory: "3Gi"\r
+            cpu: "1.8"\r
+          requests:\r
+            memory: "2Gi"\r
+            cpu: "1"\r
+        {{else}}  \r
+        resources:\r
+          limits:\r
+            memory: "6Gi"\r
+            cpu: "4"\r
+          requests:\r
+            memory: "2Gi"\r
+            cpu: "1.5"\r
+        {{ end }}\r
+        env:\r
+        - name: NAMESPACE\r
+          value: {{.Values.namespace}}\r
+        - name: APP_NAME\r
+          value: {{ .Values.appName}}\r
+        - name: AAF_PERM_TYPE\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.aafPermType.prod | quote }}\r
+          {{ else if  eq .Values.env "st"}}\r
+          value: {{ .Values.aafPermType.st | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.aafPermType.dev | quote }}\r
+          {{ end }}                 \r
+        - name: AAF_ID\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_id\r
+              optional: true\r
+        - name: AAF_MECH_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_mech_password\r
+              optional: true\r
+        - name: AAF_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: aaf_password\r
+              optional: true\r
+        - name: CADI_KEYFILE\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedSecret}}\r
+              key: keyfile_secret_path\r
+              optional: true\r
+        - name: CADI_HOSTNAME\r
+          {{if eq .Values.env "prod"}}\r
+          value: {{ .Values.cadiHostname.prod | quote }}\r
+          {{else if  eq .Values.env "prod-dr"}}\r
+          value: {{ .Values.cadiHostname.prod_dr | quote }}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.cadiHostname.st | quote }} \r
+          {{ else }}\r
+          value: {{ .Values.cadiHostname.dev | quote }}\r
+          {{ end }}\r
+        - name: APP_VERSION\r
+          value: {{.Values.version}}\r
+        - name: OTF_MONGO_HOSTS\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.mongo.prod.host | quote }}\r
+          {{ else if eq  .Values.env "st" }}\r
+          value: {{ .Values.otf.mongo.st.host | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.mongo.dev.host | quote }}\r
+          {{ end }}\r
+        - name: OTF_MONGO_USERNAME\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: mongo_username\r
+              optional: true\r
+        - name: OTF_MONGO_PASSWORD\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.appName}}\r
+              key: mongo_password\r
+              optional: true\r
+        - name: OTF_MONGO_REPLICASET\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.mongo.prod.replicaSet | quote }}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.otf.mongo.st.replicaSet | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.mongo.dev.replicaSet | quote }}\r
+          {{ end }}\r
+        - name: OTF_MONGO_DATABASE\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.otf.mongo.prod.database | quote }}\r
+          {{else if  eq .Values.env "st"}}\r
+          value: {{ .Values.otf.mongo.st.database | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.mongo.dev.database | quote }}\r
+          {{ end }}\r
+        - name: otf.camunda.host\r
+          {{if eq .Values.env "prod"}}\r
+          value: {{ .Values.otf.camunda.prod.host | quote }}\r
+          {{ else if eq  .Values.env "prod-dr" }}\r
+          value: {{ .Values.otf.camunda.prod_dr.host | quote }}\r
+          {{ else if eq  .Values.env "st" }}\r
+          value: {{ .Values.otf.camunda.st.host | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.camunda.dev.host | quote }}\r
+          {{ end }}\r
+        - name: otf.camunda.port\r
+          {{if eq .Values.env "prod"}}\r
+          value: {{ .Values.otf.camunda.prod.port | quote }}\r
+          {{ else if eq  .Values.env "prod-dr" }}\r
+          value: {{ .Values.otf.camunda.prod_dr.port | quote }}\r
+          {{ else if eq .Values.env "st"}}\r
+          value: {{ .Values.otf.camunda.st.port | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.otf.camunda.dev.port | quote }}\r
+          {{ end }}\r
+        - name: otf.camunda.executionUri\r
+          value: {{.Values.otf.camunda.executionUri | quote }}\r
+        - name: otf.camunda.pollingUri\r
+          value: {{.Values.otf.camunda.pollingUri | quote }}\r
+        - name: otf.camunda.deploymentUri\r
+          value: {{.Values.otf.camunda.deploymentUri | quote }}\r
+        - name: otf.camunda.processDefinitionKeyUri\r
+          value: {{.Values.otf.camunda.processDefinitionKeyUri | quote }}\r
+        - name: otf.camunda.deploymentDeletionUri\r
+          value: {{.Values.otf.camunda.deploymentDeletionUri | quote }}\r
+        - name: otf.camunda.testDefinitionDeletionUri\r
+          value: {{.Values.otf.camunda.testDefinitionDeletionUri | quote }}\r
+\r
+        - name: otf.camunda.uri.execute-test\r
+          value: {{.Values.otf.camunda.uri.execute_test | quote }}\r
+        - name: otf.camunda.uri.process-instance-completion-check\r
+          value: {{.Values.otf.camunda.uri.process_instance_completion_check | quote }}\r
+        - name: otf.camunda.uri.deploy-test-strategy-zip\r
+          value: {{.Values.otf.camunda.uri.deploy_test_strategy_zip | quote }}\r
+        - name: otf.camunda.uri.process-definition\r
+          value: {{.Values.otf.camunda.uri.process_definition | quote }}\r
+        - name: otf.camunda.uri.delete-test-strategy\r
+          value: {{.Values.otf.camunda.uri.delete_test_strategy | quote }}\r
+        - name: otf.camunda.uri.delete-test-strategy-test-definition-id\r
+          value: {{.Values.otf.camunda.uri.delete_test_strategy_test_definition_id | quote }}\r
+        - name: otf.camunda.uri.health\r
+          value: {{.Values.otf.camunda.uri.health | quote }}\r
+\r
+        - name: otf.api.poll-interval\r
+          value: {{.Values.otf.api.poll_interval | quote}}\r
+        - name: otf.api.poll-attempts\r
+          value: {{.Values.otf.api.poll_attempts | quote}}\r
+\r
+        - name: OTF_CERT_PATH\r
+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}\r
+          value: {{ .Values.cert.prod.path | quote }}\r
+          {{ else if eq .Values.env "st"}}\r
+          value: {{ .Values.cert.st.path | quote }}\r
+          {{ else }}\r
+          value: {{ .Values.cert.dev.path | quote }}\r
+          {{ end }}  \r
+        - name: OTF_CERT_PASS\r
+          valueFrom:\r
+            secretKeyRef:\r
+              name: {{ .Values.sharedCert}}\r
+              key: PKCS12_KEY\r
+              optional: true   \r
+        volumeMounts:\r
+        - name: {{.Values.appName}}-keyfile-volume\r
+          mountPath: /opt/secret\r
+        - name: {{.Values.appName}}-cert-volume\r
+          mountPath: /opt/cert\r
+        {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}\r
+        {{else}}\r
+        - name: logging-pvc\r
+          mountPath: "/otf/logs"\r
+        {{end}} \r
+        livenessProbe:\r
+          httpGet:\r
+            path: /otf/api/health/v1\r
+            port: https\r
+            scheme: HTTPS\r
+            httpHeaders:\r
+            - name: X-Custom-Header\r
+              value: Alive\r
+          initialDelaySeconds: 30\r
+          timeoutSeconds: 30\r
+          periodSeconds: 30\r
+        readinessProbe:\r
+          httpGet:\r
+            path: /otf/api/health/v1\r
+            port: https\r
+            scheme: HTTPS\r
+            httpHeaders:\r
+            - name: X-Custom-Header\r
+              value: Ready\r
+          initialDelaySeconds: 30\r
+          timeoutSeconds: 30\r
+          periodSeconds: 30\r
+      restartPolicy: Always\r
diff --git a/otf-service-api/helm/otf-service-api/templates/secret.yaml b/otf-service-api/helm/otf-service-api/templates/secret.yaml
new file mode 100644 (file)
index 0000000..bc77345
--- /dev/null
@@ -0,0 +1,8 @@
+apiVersion: v1\r
+kind: Secret\r
+metadata:\r
+  name: {{ .Values.appName}}\r
+type: Opaque\r
+data:\r
+  mongo_username: {{ .Values.otf.mongo.username | b64enc}}\r
+  mongo_password: {{ .Values.otf.mongo.password | b64enc}}
\ No newline at end of file
diff --git a/otf-service-api/helm/otf-service-api/templates/service.yaml b/otf-service-api/helm/otf-service-api/templates/service.yaml
new file mode 100644 (file)
index 0000000..38acf3d
--- /dev/null
@@ -0,0 +1,18 @@
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: {{ .Values.appName }}\r
+  namespace: {{ .Values.namespace}}\r
+  labels:\r
+    app: {{ .Values.appName }}\r
+    version: {{ .Values.version}}\r
+spec:\r
+  type: NodePort\r
+  ports:\r
+  - name: https\r
+    port: 8443\r
+    protocol: TCP\r
+    nodePort: {{ .Values.nodePort}}\r
+  selector:\r
+    app: {{ .Values.appName }}\r
+    version: {{ .Values.version}}\r
diff --git a/otf-service-api/helm/otf-service-api/values.yaml b/otf-service-api/helm/otf-service-api/values.yaml
new file mode 100644 (file)
index 0000000..49ee641
--- /dev/null
@@ -0,0 +1,89 @@
+appName: otf-service-api\r
+version: 0.0.1-SNAPSHOT\r
+image: otf-service-api:0.0.1-SNAPSHOT\r
+namespace: org-oran-otf\r
+nodePort: 32303\r
+replicas:\r
+  dev: 2\r
+  st: 1\r
+  prod: 2\r
+env: dev\r
+# Environment variables for the service api.\r
+otf:\r
+  mongo:\r
+    dev:\r
+      host: localhost:27017,localhost:27017,localhost:27017\r
+      replicaSet: mongoOTF\r
+      database: otf\r
+    st:\r
+      host: localhost:27017,localhost:27017,localhost:27017\r
+      replicaSet: mongoOTF\r
+      database: otf_st\r
+    prod:\r
+      host: localhost:18720,localhost:18720,localhost:18720\r
+      replicaSet: otf-rs-prod2\r
+      database: otf\r
+    username: "!"\r
+    password: "!"\r
+  camunda:\r
+    dev:\r
+      host: https://localhost\r
+      port: 31313\r
+    st:\r
+      host: https://localhost\r
+      port: 31313\r
+    prod:\r
+      host: https://localhost\r
+      port: 31313\r
+    prod_dr:\r
+      host: https://localhost\r
+      port: 31313\r
+    uri:\r
+      process_definition: rest/process-definition/key\r
+      delete_test_strategy: otf/tcu/delete-test-strategy/v1/deployment-id\r
+      delete_test_strategy_test_definition_id: otf/tcu/delete-test-strategy/v1/test-definition-id\r
+      execute_test: otf/tcu/execute/workflowRequest\r
+      deploy_test_strategy_zip: otf/tcu/deploy-test-strategy-zip/v1\r
+      process_instance_completion_check: otf/tcu/process-instance-completion-check/v1\r
+      health: /otf/health/v1\r
+    executionUri: otf/tcu/execute-test/v1\r
+    pollingUri: otf/tcu/process-instance-completion-check/v1\r
+    deploymentUri: otf/tcu/deploy-test-strategy-zip/v1\r
+    processDefinitionKeyUri: rest/process-definition/key\r
+    deploymentDeletionUri: otf/tcu/delete-test-strategy/v1/deployment-id\r
+    testDefinitionDeletionUri: otf/tcu/delete-test-strategy/v1/test-definition-id\r
+  api:\r
+    poll_interval: 6000\r
+    poll_attempts: 50\r
+\r
+# permission type for aaf\r
+aafPermType:\r
+  dev: org.oran.otf.svcapi\r
+  st:  org.oran.otf.st.svcapi\r
+  prod:  org.oran.otf.prod.svcapi\r
+\r
+cadiHostname:\r
+  dev: localhost\r
+  st: localhost\r
+  prod: localhost\r
+  prod_dr: localhost\r
+  \r
+# Secret related information.\r
+sharedSecret: otf-aaf-credential-generator\r
+sharedCert: otf-cert-secret-builder\r
+cert:\r
+  dev: \r
+    name: otf_dev.p12\r
+    path: opt/cert/otf_dev.p12\r
+  st: \r
+    name: otf_st.p12\r
+    path: opt/cert/otf_st.p12\r
+  prod: \r
+    name: otf_prod.p12\r
+    path: opt/cert/otf_prod.p12\r
+  \r
+pvc:\r
+  dev: org-oran-otf-dev-logs-pv\r
+  prod: org-oran-otf-prod-logs-pv\r
+\r
+\r
diff --git a/otf-service-api/pom.xml b/otf-service-api/pom.xml
new file mode 100644 (file)
index 0000000..fda9ed3
--- /dev/null
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xmlns="http://maven.apache.org/POM/4.0.0"\r
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">\r
+  <artifactId>otf-service-api</artifactId>\r
+  <build>\r
+    <finalName>otf-service-api</finalName>\r
+    <plugins>\r
+      <plugin>\r
+        <artifactId>maven-compiler-plugin</artifactId>\r
+        <configuration>\r
+          <source>1.8</source>\r
+          <target>1.8</target>\r
+        </configuration>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+      </plugin>\r
+      <plugin>\r
+        <artifactId>spring-boot-maven-plugin</artifactId>\r
+        <groupId>org.springframework.boot</groupId>\r
+      </plugin>\r
+      <plugin>\r
+        <artifactId>swagger-maven-plugin</artifactId>\r
+        <configuration>\r
+          <outputFileName>openapi</outputFileName>\r
+          <!--<outputPath>${project.build.directory}/generatedtest</outputPath>-->\r
+          <outputFormat>JSONANDYAML</outputFormat>\r
+          <prettyPrint>true</prettyPrint>\r
+          <resourcePackages>\r
+            <package>org.oran.otf.api</package>\r
+          </resourcePackages>\r
+        </configuration>\r
+        <executions>\r
+          <execution>\r
+            <goals>\r
+              <goal>resolve</goal>\r
+            </goals>\r
+            <phase>compile</phase>\r
+          </execution>\r
+        </executions>\r
+        <groupId>io.swagger.core.v3</groupId>\r
+        <version>2.0.7</version>\r
+      </plugin>\r
+\r
+      <plugin>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+        <artifactId>maven-surefire-plugin</artifactId>\r
+        <version>2.22.1</version>\r
+        <configuration>\r
+          <skipTests>${skipUTs}</skipTests>\r
+        </configuration>\r
+      </plugin>\r
+      <plugin>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+        <artifactId>maven-failsafe-plugin</artifactId>\r
+        <version>2.22.1</version>\r
+        <executions>\r
+          <execution>\r
+            <id>run-integration-tests</id>\r
+            <phase>integration-test</phase>\r
+            <goals>\r
+              <goal>integration-test</goal>\r
+              <goal>verify</goal>\r
+            </goals>\r
+          </execution>\r
+        </executions>\r
+        <configuration>\r
+          <skipTests>${skipTests}</skipTests>\r
+          <skipITs>${skipITs}</skipITs>\r
+        </configuration>\r
+      </plugin>\r
+\r
+    </plugins>\r
+    <resources>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <excludes>\r
+          <exclude>otf_dev.p12</exclude>\r
+        </excludes>\r
+        <filtering>true</filtering>\r
+        <includes>\r
+          <include>**/*</include>\r
+        </includes>\r
+        <targetPath>${basedir}/target/src/main/resources</targetPath>\r
+      </resource>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <excludes>\r
+          <exclude>otf_dev.p12</exclude>\r
+        </excludes>\r
+        <filtering>true</filtering>\r
+        <includes>\r
+          <include>**/*</include>\r
+        </includes>\r
+      </resource>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <includes>\r
+          <include>otf_dev.p12</include>\r
+        </includes>\r
+        <targetPath>${basedir}/target/src/main/resources</targetPath>\r
+      </resource>\r
+      <resource>\r
+        <directory>src/main/resources</directory>\r
+        <includes>\r
+          <include>otf_dev.p12</include>\r
+        </includes>\r
+      </resource>\r
+      <resource>\r
+        <directory>docker</directory>\r
+        <includes>\r
+          <include>Dockerfile</include>\r
+        </includes>\r
+        <targetPath>${basedir}/target</targetPath>\r
+      </resource>\r
+    </resources>\r
+  </build>\r
+  <dependencies>\r
+    <dependency>\r
+      <artifactId>spring-boot-starter</artifactId>\r
+      <groupId>org.springframework.boot</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>spring-boot-starter-web</artifactId>\r
+      <groupId>org.springframework.boot</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>spring-boot-starter-jersey</artifactId>\r
+      <groupId>org.springframework.boot</groupId>\r
+    </dependency>\r
+\r
+\r
+    <dependency>\r
+      <groupId>org.glassfish.jersey.test-framework</groupId>\r
+      <artifactId>jersey-test-framework-core</artifactId>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.glassfish.jersey.test-framework.providers</groupId>\r
+      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>de.flapdoodle.embed</groupId>\r
+      <artifactId>de.flapdoodle.embed.mongo</artifactId>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>com.github.tomakehurst</groupId>\r
+      <artifactId>wiremock-jre8</artifactId>\r
+      <version>2.24.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.mockito</groupId>\r
+      <artifactId>mockito-core</artifactId>\r
+      <version>2.15.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.mockito</groupId>\r
+      <artifactId>mockito-inline</artifactId>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>io.rest-assured</groupId>\r
+      <artifactId>rest-assured</artifactId>\r
+      <version>4.0.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>io.rest-assured</groupId>\r
+      <artifactId>rest-assured-all</artifactId>\r
+      <version>4.0.0</version>\r
+      <scope>test</scope>\r
+    </dependency>\r
+\r
+\r
+\r
+    <dependency>\r
+      <artifactId>spring-boot-starter-test</artifactId>\r
+      <groupId>org.springframework.boot</groupId>\r
+      <scope>test</scope>\r
+      <exclusions>\r
+        <exclusion>\r
+          <groupId>com.vaadin.external.google</groupId>\r
+          <artifactId>android-json</artifactId>\r
+        </exclusion>\r
+      </exclusions>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>spring-boot-starter-data-mongodb</artifactId>\r
+      <groupId>org.springframework.boot</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>swagger-jaxrs2</artifactId>\r
+      <groupId>io.swagger.core.v3</groupId>\r
+      <version>2.0.7</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>swagger-jaxrs2-servlet-initializer</artifactId>\r
+      <groupId>io.swagger.core.v3</groupId>\r
+      <version>2.0.7</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>swagger-annotations</artifactId>\r
+      <groupId>io.swagger.core.v3</groupId>\r
+      <version>2.0.7</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>springfox-swagger2</artifactId>\r
+      <groupId>io.springfox</groupId>\r
+      <version>2.9.2</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>springfox-swagger-ui</artifactId>\r
+      <groupId>io.springfox</groupId>\r
+      <version>2.9.2</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>springfox-bean-validators</artifactId>\r
+      <groupId>io.springfox</groupId>\r
+      <version>2.9.2</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>httpclient</artifactId>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>h2</artifactId>\r
+      <groupId>com.h2database</groupId>\r
+    </dependency>\r
+\r
+<!--    <dependency>-->\r
+<!--      <artifactId>wiremock</artifactId>-->\r
+<!--      <groupId>com.github.tomakehurst</groupId>-->\r
+<!--      <version>1.58</version>-->\r
+<!--    </dependency>-->\r
+\r
+    <dependency>\r
+      <artifactId>gson</artifactId>\r
+      <groupId>com.google.code.gson</groupId>\r
+      <version>2.8.5</version>\r
+    </dependency>\r
+\r
+    <!-- CADI AAF Dependencies !! -->\r
+    <dependency>\r
+      <artifactId>aaf-auth-client</artifactId>\r
+      <groupId>org.onap.aaf.authz</groupId>\r
+      <version>${cadi.version}</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>aaf-cadi-core</artifactId>\r
+      <groupId>org.onap.aaf.authz</groupId>\r
+      <version>${cadi.version}</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>aaf-cadi-aaf</artifactId>\r
+      <groupId>org.onap.aaf.authz</groupId>\r
+      <version>${cadi.version}</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>json</artifactId>\r
+      <groupId>org.json</groupId>\r
+      <version>20180813</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>jackson-annotations</artifactId>\r
+      <groupId>com.fasterxml.jackson.core</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>jackson-core</artifactId>\r
+      <groupId>com.fasterxml.jackson.core</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>jackson-databind</artifactId>\r
+      <groupId>com.fasterxml.jackson.core</groupId>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>jersey-media-multipart</artifactId>\r
+      <groupId>org.glassfish.jersey.media</groupId>\r
+      <version>2.27</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>httpmime</artifactId>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+      <version>4.5.7-SNAPSHOT</version>\r
+    </dependency>\r
+\r
+    <dependency>\r
+      <artifactId>httpasyncclient</artifactId>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+      <version>4.1.4</version>\r
+    </dependency>\r
+      <dependency>\r
+          <groupId>net.java.dev.jna</groupId>\r
+          <artifactId>jna-platform</artifactId>\r
+      </dependency>\r
+  </dependencies>\r
+\r
+  <description>Service API - OTF</description>\r
+  <groupId>org.oran.otf</groupId>\r
+\r
+  <modelVersion>4.0.0</modelVersion>\r
+\r
+  <name>otf-service-api</name>\r
+\r
+  <packaging>jar</packaging>\r
+\r
+  <parent>\r
+    <artifactId>spring-boot-starter-parent</artifactId>\r
+    <groupId>org.springframework.boot</groupId>\r
+    <version>2.1.3.RELEASE</version>\r
+  </parent>\r
+\r
+  <properties>\r
+    <skipTests>false</skipTests>\r
+    <skipITs>${skipTests}</skipITs>\r
+    <skipUTs>${skipTests}</skipUTs>\r
+    <cadi.version>2.1.10</cadi.version>\r
+    <docker.registry>registry.hub.docker.io</docker.registry>\r
+    <java.version>1.8</java.version>\r
+    <namespace>org.oran.otf</namespace>\r
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\r
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\r
+  </properties>\r
+  <version>Camille.1.0</version>\r
+\r
+\r
+</project>
\ No newline at end of file
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/Application.java b/otf-service-api/src/main/java/org/oran/otf/api/Application.java
new file mode 100644 (file)
index 0000000..8836555
--- /dev/null
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api;\r
+\r
+import static com.google.common.collect.Sets.newHashSet;\r
+\r
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;\r
+import io.swagger.v3.oas.annotations.info.Contact;\r
+import io.swagger.v3.oas.annotations.info.Info;\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+import org.springframework.boot.SpringApplication;\r
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.SpringBootApplication;\r
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;\r
+import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;\r
+import org.springframework.context.ApplicationContext;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.ComponentScan;\r
+import org.springframework.context.annotation.Configuration;\r
+import springfox.documentation.builders.PathSelectors;\r
+import springfox.documentation.spi.DocumentationType;\r
+import springfox.documentation.spring.web.plugins.Docket;\r
+import springfox.documentation.swagger2.annotations.EnableSwagger2;\r
+\r
+@SpringBootApplication\r
+@Configuration\r
+@EnableAutoConfiguration(\r
+    exclude = {\r
+      ErrorMvcAutoConfiguration.class,\r
+      DataSourceAutoConfiguration.class,\r
+      HibernateJpaAutoConfiguration.class,\r
+    })\r
+@ComponentScan(basePackages = "org.oran.otf")\r
+@EnableSwagger2\r
+@OpenAPIDefinition(\r
+    info =\r
+        @Info(\r
+            title = "Open Test Framework API",\r
+            version = "1.0",\r
+            description = "A RESTful API used to communicate with the OTF test control unit.",\r
+            contact = @Contact(url = "https://localhost:32524", name = "OTF")))\r
+public class Application {\r
+  private static final Log log = LogFactory.getLog(Application.class);\r
+\r
+  public static void main(String[] args) {\r
+    ApplicationContext ctx = SpringApplication.run(Application.class, args);\r
+  }\r
+\r
+  @Bean\r
+  public Docket testInstanceApi() {\r
+    return new Docket(DocumentationType.SWAGGER_2)\r
+        .select()\r
+        // .apis(testInstancePath())\r
+        .paths(PathSelectors.any())\r
+        .build()\r
+        .protocols(newHashSet("https"));\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/Utilities.java b/otf-service-api/src/main/java/org/oran/otf/api/Utilities.java
new file mode 100644 (file)
index 0000000..1279688
--- /dev/null
@@ -0,0 +1,394 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api;\r
+\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import org.oran.otf.common.repository.UserRepository;\r
+import com.fasterxml.jackson.databind.DeserializationFeature;\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import com.google.common.base.Strings;\r
+import com.google.gson.JsonObject;\r
+import com.google.gson.JsonParseException;\r
+import com.google.gson.JsonParser;\r
+import java.io.IOException;\r
+import java.io.PrintWriter;\r
+import java.io.StringWriter;\r
+import java.util.Arrays;\r
+import java.util.Base64;\r
+import java.util.Date;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+import org.apache.http.HttpEntity;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.NameValuePair;\r
+import org.apache.http.client.HttpClient;\r
+import org.apache.http.client.entity.UrlEncodedFormEntity;\r
+import org.apache.http.client.methods.HttpDelete;\r
+import org.apache.http.client.methods.HttpGet;\r
+import org.apache.http.client.methods.HttpPost;\r
+import org.apache.http.conn.ssl.NoopHostnameVerifier;\r
+import org.apache.http.entity.StringEntity;\r
+import org.apache.http.impl.client.HttpClientBuilder;\r
+import org.apache.http.message.BasicNameValuePair;\r
+import org.apache.http.util.EntityUtils;\r
+import org.bson.types.ObjectId;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public class Utilities {\r
+\r
+  public static JsonObject parseJson(String str) {\r
+    try {\r
+      return new JsonParser().parse(str).getAsJsonObject();\r
+    } catch (JsonParseException jpe) {\r
+      logger.error("Cannot parse string as Json.");\r
+      return null;\r
+    }\r
+  }\r
+\r
+  public static class Http {\r
+    public static class BuildResponse {\r
+      public static Response badRequest() {\r
+        return Response.status(400).build();\r
+      }\r
+\r
+      public static Response badRequestWithMessage(String msg) {\r
+        return Response.status(400)\r
+            .type(MediaType.APPLICATION_JSON)\r
+            .entity(new OTFApiResponse(400, msg))\r
+            .build();\r
+      }\r
+\r
+      public static Response internalServerError() {\r
+        return Response.status(500).build();\r
+      }\r
+\r
+      public static Response internalServerErrorWithMessage(String msg) {\r
+        return Response.status(500)\r
+            .type(MediaType.APPLICATION_JSON)\r
+            .entity(new OTFApiResponse(500, msg))\r
+            .build();\r
+      }\r
+\r
+      public static Response unauthorized() {\r
+        return Response.status(401).build();\r
+      }\r
+\r
+      public static Response unauthorizedWithMessage(String msg) {\r
+        return Response.status(401)\r
+            .type(MediaType.APPLICATION_JSON)\r
+            .entity(new OTFApiResponse(401, msg))\r
+            .build();\r
+      }\r
+    }\r
+\r
+    public static HttpResponse httpPostJsonUsingAAF(String url, String body) throws Exception {\r
+      HttpResponse response = null;\r
+\r
+        String aafCredentialsDecoded =\r
+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");\r
+\r
+        HttpPost post = new HttpPost(url);\r
+        post.setHeader("Content-Type", MediaType.APPLICATION_JSON);\r
+        post.setHeader(\r
+            "Authorization",\r
+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));\r
+        post.setEntity(new StringEntity(body));\r
+\r
+        HttpClient client =\r
+            HttpClientBuilder.create()\r
+                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)\r
+                .build();\r
+        response = client.execute(post);\r
+\r
+        // logger.info(String.format("[POST:%s]\n %s", url, body));\r
+\r
+      return response;\r
+    }\r
+\r
+    public static HttpResponse httpDeleteAAF(String url) {\r
+      HttpResponse response = null;\r
+\r
+      try {\r
+        String aafCredentialsDecoded =\r
+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");\r
+\r
+        HttpDelete delete = new HttpDelete(url);\r
+        delete.setHeader(\r
+            "Authorization",\r
+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));\r
+        HttpClient client =\r
+            HttpClientBuilder.create()\r
+                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)\r
+                .build();\r
+        response = client.execute(delete);\r
+\r
+        // logger.info(String.format("[DELETE:%s]\n", url));\r
+      } catch (Exception e) {\r
+        e.printStackTrace();\r
+      }\r
+\r
+      return response;\r
+    }\r
+\r
+    public static HttpResponse httpPostXmlUsingAAF(String url, String body) {\r
+      HttpResponse response = null;\r
+\r
+      try {\r
+        String aafCredentialsDecoded =\r
+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");\r
+\r
+        HttpPost post = new HttpPost(url);\r
+        post.setHeader("Content-Type", MediaType.APPLICATION_JSON);\r
+        post.setHeader(\r
+            "Authorization",\r
+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));\r
+        post.setEntity(new StringEntity(body));\r
+\r
+        List<NameValuePair> urlParameters = Arrays.asList(new BasicNameValuePair("xml", body));\r
+        post.setEntity(new UrlEncodedFormEntity(urlParameters));\r
+\r
+        HttpClient client = HttpClientBuilder.create().build();\r
+        response = client.execute(post);\r
+\r
+        logger.info(String.format("[POST:%s]\n %s", url, body));\r
+      } catch (Exception e) {\r
+        e.printStackTrace();\r
+      }\r
+\r
+      return response;\r
+    }\r
+\r
+    public static HttpResponse httpGetUsingAAF(String url) {\r
+      HttpResponse response = null;\r
+\r
+      try {\r
+        String aafCredentialsDecoded =\r
+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");\r
+\r
+        HttpGet get = new HttpGet(url);\r
+        get.setHeader("Content-Type", "application/json");\r
+        get.setHeader(\r
+            "Authorization",\r
+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));\r
+\r
+        HttpClient client =\r
+            HttpClientBuilder.create()\r
+                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)\r
+                .build();\r
+        response = client.execute(get);\r
+\r
+        logger.info(String.format("[GET:%s]", url));\r
+      } catch (Exception e) {\r
+        e.printStackTrace();\r
+      }\r
+\r
+      return response;\r
+    }\r
+  }\r
+\r
+  public static class Camunda {\r
+\r
+    public static boolean isCamundaOnline() {\r
+      final String healthUrl =\r
+          String.format(\r
+              "%s:%s/%s",\r
+              System.getenv("otf.camunda.host"),\r
+              System.getenv("otf.camunda.port"),\r
+              System.getenv("otf.camunda.uri.health"));\r
+\r
+      HttpResponse res = Utilities.Http.httpGetUsingAAF(healthUrl);\r
+      return res != null && res.getStatusLine().getStatusCode() == 200;\r
+    }\r
+\r
+    public static JsonObject processInstanceStatus(String executionId) {\r
+      // Read necessary environment variables - Avoiding using Spring dependencies (@Value)\r
+      String host = System.getenv("otf.camunda.host");\r
+      String path = System.getenv("otf.camunda.uri.process-instance-completion-check");\r
+      int port = Utilities.TryGetEnvironmentVariable("otf.camunda.port");\r
+\r
+      if (!Utilities.isHostValid(host)) {\r
+        logger.error("Host (%s) must use either the http or https protocol.", host);\r
+        return null;\r
+      }\r
+\r
+      if (!Utilities.isPortValid(port)) {\r
+        logger.error(\r
+            "Invalid port (%s) specified as environment variable 'otf.camunda.port'.",\r
+            System.getenv("otf.camunda.port"));\r
+        return null;\r
+      }\r
+      try {\r
+        String getUrl = String.format("%s:%s/%s/%s", host, port, path, executionId);\r
+        HttpResponse response = Utilities.Http.httpGetUsingAAF(getUrl);\r
+        HttpEntity entity = response.getEntity();\r
+        String result = EntityUtils.toString(entity);\r
+\r
+        return parseJson(result);\r
+      } catch (IOException ioe) {\r
+        Utilities.printStackTrace(ioe, Utilities.LogLevel.ERROR);\r
+        logger.error("Cannot convert http entity to String.");\r
+      } catch (Exception e) {\r
+        Utilities.printStackTrace(e, Utilities.LogLevel.ERROR);\r
+      }\r
+      // conversion was unsuccessful\r
+      return null;\r
+    }\r
+  }\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(Utilities.class);\r
+\r
+  public static void printStackTrace(Exception exception, LogLevel logLevel) {\r
+    String stackTrace = getStackTrace(exception);\r
+\r
+    switch (logLevel) {\r
+      case INFO:\r
+        logger.info(stackTrace);\r
+        break;\r
+      case WARN:\r
+        logger.warn(stackTrace);\r
+        break;\r
+      case DEBUG:\r
+        logger.debug(stackTrace);\r
+        break;\r
+      case ERROR:\r
+        logger.error(stackTrace);\r
+        break;\r
+    }\r
+  }\r
+\r
+  public static int TryGetEnvironmentVariable(String variable) {\r
+    String value = System.getenv(variable);\r
+    int result = 0x80000000;\r
+\r
+    try {\r
+      result = Integer.parseInt(value);\r
+    } catch (NumberFormatException error) {\r
+      error.printStackTrace();\r
+      logger.error(error.getMessage());\r
+    }\r
+\r
+    return result;\r
+  }\r
+\r
+  public static String getStackTrace(Exception exception) {\r
+    StringWriter stringWriter = new StringWriter();\r
+    exception.printStackTrace(new PrintWriter(stringWriter));\r
+    return stringWriter.toString();\r
+  }\r
+\r
+  public static boolean isObjectIdValid(String input) {\r
+    ObjectId id = null;\r
+    try {\r
+      id = new ObjectId(input); // check if an ObjectId can be created from the string\r
+      if (id.toString().equalsIgnoreCase(input)) return true;\r
+      logger.warn("The input string does not have the same value as it's string representation.");\r
+    } catch (IllegalArgumentException e) {\r
+      logger.error(String.format("An ObjectId cannot be instantiated from the string: %s", input));\r
+    }\r
+\r
+    return false;\r
+  }\r
+\r
+  public static boolean isPortValid(int port) {\r
+    return (port >= 0 && port <= 65535);\r
+  }\r
+\r
+  public static boolean isHostValid(String host) {\r
+    return host.startsWith("http");\r
+  }\r
+\r
+  public static <T> boolean identifierExistsInCollection(\r
+      MongoRepository<T, String> repository, ObjectId identifier) {\r
+    return repository.findById(identifier.toString()).isPresent();\r
+  }\r
+\r
+  public static <T> T findByIdGeneric(MongoRepository<T, String> repository, ObjectId identifier) {\r
+    Optional<T> optionalObj = repository.findById(identifier.toString());\r
+    return optionalObj.orElse(null);\r
+  }\r
+\r
+  public static String[] decodeBase64AuthorizationHeader(String encodedHeader) {\r
+    try {\r
+      byte[] decodedAuthorization = Base64.getDecoder().decode(encodedHeader.replace("Basic ", ""));\r
+      String credentials = new String(decodedAuthorization);\r
+      return credentials.split(":");\r
+    } catch (Exception e) {\r
+      logger.error("Unable to decode authorization header: " + encodedHeader);\r
+      return null;\r
+    }\r
+  }\r
+\r
+  public static User findUserByMechanizedId(String mechanizedId, UserRepository userRepository) {\r
+    Optional<User> optionalUser = userRepository.findFirstByEmail(mechanizedId);\r
+    return optionalUser.orElse(null);\r
+  }\r
+\r
+  public static User findUserByAuthHeader(String authorization, UserRepository userRepository) {\r
+    try {\r
+      if (Strings.isNullOrEmpty(authorization)) {\r
+        return null;\r
+      }\r
+      String[] credentials = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+      return findUserByMechanizedId(credentials[0], userRepository);\r
+    } catch (Exception e) {\r
+      return null;\r
+    }\r
+  }\r
+\r
+  public static <T> T resolveOptional(Optional<T> optional) {\r
+    return optional.orElse(null);\r
+  }\r
+\r
+  public static <T> T mapRequest(Class<T> targetType, String input) {\r
+    logger.info(targetType.getName());\r
+\r
+    ObjectMapper mapper =\r
+        new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\r
+    try {\r
+      return mapper.readValue(input, targetType);\r
+    } catch (IOException e) {\r
+      Utilities.printStackTrace(e, LogLevel.ERROR);\r
+      return null;\r
+    }\r
+  }\r
+\r
+  public enum LogLevel {\r
+    WARN,\r
+    DEBUG,\r
+    INFO,\r
+    ERROR\r
+  }\r
+\r
+  public static Date getCurrentDate() {\r
+    return new Date(System.currentTimeMillis());\r
+  }\r
+\r
+  public static Map<String, Object> replaceObjectId(Map<String, Object> map, String objectIdKey) {\r
+    if (map.containsKey(objectIdKey)) {\r
+      ObjectId id = (ObjectId) map.get(objectIdKey);\r
+      map.replace(objectIdKey, id.toString());\r
+    }\r
+\r
+    return map;\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/CadiFilterConfiguration.java b/otf-service-api/src/main/java/org/oran/otf/api/config/CadiFilterConfiguration.java
new file mode 100644 (file)
index 0000000..d98b9ed
--- /dev/null
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import javax.servlet.Filter;\r
+import org.onap.aaf.cadi.Access;\r
+import org.onap.aaf.cadi.config.Config;\r
+import org.onap.aaf.cadi.filter.CadiFilter;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\r
+import org.springframework.boot.web.servlet.FilterRegistrationBean;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Conditional;\r
+import org.springframework.context.annotation.PropertySource;\r
+import org.springframework.stereotype.Component;\r
+\r
+@PropertySource("classpath:application.properties")\r
+@Component\r
+@ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true",matchIfMissing = true)\r
+public class CadiFilterConfiguration {\r
+\r
+  @Value("${aaf.call-timeout}")\r
+  private String AAF_CALL_TIMEOUT;\r
+\r
+  @Value("${aaf.conn-timeout}")\r
+  private String AAF_CONN_TIMEOUT;\r
+\r
+  @Value("${aaf.default-realm}")\r
+  private String AAF_DEFAULT_REALM;\r
+\r
+  @Value("${aaf.env}")\r
+  private String AAF_ENV;\r
+\r
+  @Value("${aaf.locate-url}")\r
+  private String AAF_LOCATE_URL;\r
+\r
+  @Value("${aaf.lur-class}")\r
+  private String AAF_LUR_CLASS;\r
+\r
+  @Value("${aaf.url}")\r
+  private String AAF_URL;\r
+\r
+  @Value("${basic-realm}")\r
+  private String BASIC_REALM;\r
+\r
+  @Value("${basic-warn}")\r
+  private String BASIC_WARN;\r
+\r
+  @Value("${cadi-latitude}")\r
+  private String CADI_LATITUDE;\r
+\r
+  @Value("${cadi-longitude}")\r
+  private String CADI_LONGITUDE;\r
+\r
+  @Value("${cadi-protocols}")\r
+  private String CADI_PROTOCOLS;\r
+\r
+  @Value("${cadi-noauthn}")\r
+  private String CADI_NOAUTHN;\r
+\r
+  @Bean(name = "cadiFilterRegistrationBean")\r
+  @ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true",matchIfMissing = true)\r
+  public FilterRegistrationBean<Filter> cadiFilterRegistration() {\r
+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();\r
+    // set cadi configuration properties\r
+    initCadiProperties(registration);\r
+\r
+    registration.addUrlPatterns("/otf/api/testInstance/*", "/otf/api/testExecution/*", "/otf/api/testStrategy/*", "/otf/api/virtualTestHead/*");\r
+    registration.setFilter(cadiFilter());\r
+    registration.setName("otfCadiFilter");\r
+    registration.setOrder(0);\r
+    return registration;\r
+  }\r
+\r
+  public Filter cadiFilter() {\r
+    return new CadiFilter();\r
+  }\r
+\r
+  private void initCadiProperties(FilterRegistrationBean<Filter> registration) {\r
+    registration.addInitParameter(Config.AAF_APPID, System.getenv("AAF_ID"));\r
+    registration.addInitParameter(Config.AAF_APPPASS, System.getenv("AAF_PASSWORD"));\r
+    registration.addInitParameter(Config.AAF_CALL_TIMEOUT, AAF_CALL_TIMEOUT);\r
+    registration.addInitParameter(Config.AAF_CONN_TIMEOUT, AAF_CONN_TIMEOUT);\r
+    registration.addInitParameter(Config.AAF_DEFAULT_REALM, AAF_DEFAULT_REALM);\r
+    registration.addInitParameter(Config.AAF_ENV, AAF_ENV);\r
+    registration.addInitParameter(Config.AAF_LOCATE_URL, AAF_LOCATE_URL);\r
+    registration.addInitParameter(Config.AAF_LUR_CLASS, AAF_LUR_CLASS);\r
+    registration.addInitParameter(\r
+        Config.AAF_URL, AAF_URL);\r
+\r
+    registration.addInitParameter(Config.BASIC_REALM, BASIC_REALM);\r
+    registration.addInitParameter(Config.BASIC_WARN, BASIC_WARN);\r
+\r
+    registration.addInitParameter(Config.CADI_KEYFILE, System.getenv("CADI_KEYFILE"));\r
+    registration.addInitParameter(Config.CADI_LATITUDE, CADI_LATITUDE);\r
+    //registration.addInitParameter(Config.CADI_LOGLEVEL, Access.Level.DEBUG.name());\r
+    registration.addInitParameter(Config.CADI_LONGITUDE, CADI_LONGITUDE);\r
+    registration.addInitParameter(Config.CADI_NOAUTHN, CADI_NOAUTHN);\r
+    registration.addInitParameter(Config.CADI_PROTOCOLS, CADI_PROTOCOLS);\r
+    registration.addInitParameter(Config.CADI_KEYSTORE, System.getenv("OTF_CERT_PATH"));\r
+    registration.addInitParameter(Config.CADI_KEYSTORE_PASSWORD, System.getenv("OTF_CERT_PASS"));\r
+\r
+    registration.addInitParameter(Config.HOSTNAME, System.getenv("CADI_HOSTNAME"));\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/CombinedResourceProvider.java b/otf-service-api/src/main/java/org/oran/otf/api/config/CombinedResourceProvider.java
new file mode 100644 (file)
index 0000000..ef7fae5
--- /dev/null
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import java.util.List;\r
+import java.util.stream.Collectors;\r
+import java.util.stream.Stream;\r
+import javax.annotation.Resource;\r
+import org.springframework.context.annotation.Primary;\r
+import org.springframework.stereotype.Component;\r
+import springfox.documentation.swagger.web.InMemorySwaggerResourcesProvider;\r
+import springfox.documentation.swagger.web.SwaggerResource;\r
+import springfox.documentation.swagger.web.SwaggerResourcesProvider;\r
+\r
+@Component\r
+@Primary\r
+public class CombinedResourceProvider implements SwaggerResourcesProvider {\r
+\r
+  @Resource private InMemorySwaggerResourcesProvider inMemorySwaggerResourcesProvider;\r
+\r
+  public List<SwaggerResource> get() {\r
+\r
+    SwaggerResource jerseySwaggerResource = new SwaggerResource();\r
+    jerseySwaggerResource.setLocation("/otf/api/openapi.json");\r
+    jerseySwaggerResource.setSwaggerVersion("2.0");\r
+    jerseySwaggerResource.setName("Service API");\r
+\r
+    return Stream.concat(\r
+            Stream.of(jerseySwaggerResource), inMemorySwaggerResourcesProvider.get().stream())\r
+        .collect(Collectors.toList());\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/DataConfig.java b/otf-service-api/src/main/java/org/oran/otf/api/config/DataConfig.java
new file mode 100644 (file)
index 0000000..0546a7d
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import com.mongodb.MongoClient;\r
+import com.mongodb.MongoClientOptions;\r
+import com.mongodb.MongoCredential;\r
+import com.mongodb.ServerAddress;\r
+import java.util.ArrayList;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Profile;\r
+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;\r
+\r
+@Configuration\r
+@EnableMongoRepositories(basePackages = "org.oran.otf.common.repository")\r
+@Profile("!test")\r
+public class DataConfig extends AbstractMongoConfiguration {\r
+\r
+  @Value("${otf.mongo.hosts}")\r
+  private String hosts;\r
+\r
+  @Value("${otf.mongo.username}")\r
+  private String username;\r
+\r
+  @Value("${otf.mongo.password}")\r
+  private String password;\r
+\r
+  @Value("${otf.mongo.replicaSet}")\r
+  private String replicaSet;\r
+\r
+  @Value("${otf.mongo.database}")\r
+  private String database;\r
+\r
+  public DataConfig() {}\r
+\r
+  @Override\r
+  protected String getDatabaseName() {\r
+    return database;\r
+  }\r
+\r
+  @Override\r
+  public MongoClient mongoClient() {\r
+    MongoCredential credential =\r
+        MongoCredential.createScramSha1Credential(username, database, password.toCharArray());\r
+\r
+    MongoClientOptions options =\r
+        MongoClientOptions.builder().sslEnabled(false).requiredReplicaSetName(replicaSet).build();\r
+\r
+    String[] hostArray = hosts.split(",");\r
+    ArrayList<ServerAddress> hosts = new ArrayList<>();\r
+\r
+    for (String host : hostArray) {\r
+      String[] hostSplit = host.split(":");\r
+      hosts.add(new ServerAddress(hostSplit[0], Integer.parseInt(hostSplit[1])));\r
+    }\r
+\r
+    return new MongoClient(hosts, credential, options);\r
+  }\r
+\r
+  @Override\r
+  @Bean\r
+  public MongoTemplate mongoTemplate() {\r
+    return new MongoTemplate(mongoClient(), database);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/HttpSecurityConfiguration.java b/otf-service-api/src/main/java/org/oran/otf/api/config/HttpSecurityConfiguration.java
new file mode 100644 (file)
index 0000000..2646431
--- /dev/null
@@ -0,0 +1,68 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import org.apache.catalina.Context;\r
+import org.apache.catalina.connector.Connector;\r
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;\r
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.context.properties.EnableConfigurationProperties;\r
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;\r
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+\r
+@Configuration\r
+@EnableConfigurationProperties\r
+public class HttpSecurityConfiguration {\r
+  @Value("${server.port.http}")\r
+  private int httpPort;\r
+\r
+  @Value("${server.port}")\r
+  private int httpsPort;\r
+\r
+  @Value("${ssl.flag}")\r
+  private boolean httpsOnly;\r
+\r
+  @Bean\r
+  public ServletWebServerFactory servletContainer() {\r
+    TomcatServletWebServerFactory tomcat =\r
+        new TomcatServletWebServerFactory(){\r
+          @Override\r
+          protected void postProcessContext(Context context) {\r
+            SecurityConstraint securityConstraint = new SecurityConstraint();\r
+            if(httpsOnly){ securityConstraint.setUserConstraint("CONFIDENTIAL");}\r
+            SecurityCollection collection = new SecurityCollection();\r
+            collection.addPattern("/*");\r
+            securityConstraint.addCollection(collection);\r
+            context.addConstraint(securityConstraint);\r
+          }\r
+        };\r
+    tomcat.addAdditionalTomcatConnectors(redirectConnector());\r
+    return tomcat;\r
+  }\r
+\r
+  private Connector redirectConnector() {\r
+    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");\r
+    connector.setScheme("http");\r
+    connector.setPort(httpPort);\r
+    connector.setSecure(false);\r
+    if(httpsOnly) { connector.setRedirectPort(httpsPort); }\r
+    return connector;\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/JerseyConfiguration.java b/otf-service-api/src/main/java/org/oran/otf/api/config/JerseyConfiguration.java
new file mode 100644 (file)
index 0000000..6d06ac7
--- /dev/null
@@ -0,0 +1,99 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import org.oran.otf.api.service.impl.*;\r
+import com.fasterxml.jackson.annotation.JsonInclude;\r
+import com.fasterxml.jackson.databind.DeserializationFeature;\r
+import com.fasterxml.jackson.databind.MapperFeature;\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import com.fasterxml.jackson.databind.SerializationFeature;\r
+import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;\r
+\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+import javax.ws.rs.ApplicationPath;\r
+import org.glassfish.jersey.logging.LoggingFeature;\r
+import org.glassfish.jersey.media.multipart.MultiPartFeature;\r
+import org.glassfish.jersey.server.ResourceConfig;\r
+import org.glassfish.jersey.server.ServerProperties;\r
+import org.glassfish.jersey.servlet.ServletProperties;\r
+import org.oran.otf.api.service.impl.*;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Primary;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+@ApplicationPath("/otf/api")\r
+public class JerseyConfiguration extends ResourceConfig {\r
+  private static final Logger log = Logger.getLogger(JerseyConfiguration.class.getName());\r
+\r
+  //   @Value("${spring.jersey.application-path}")\r
+  //   private String apiPath;\r
+\r
+  //  @Value("${springfox.documentation.swagger.v2.path}")\r
+  //  private String swagger2Endpoint;\r
+\r
+  @Autowired\r
+  public JerseyConfiguration() {\r
+    registerFeatures();\r
+    registerEndpoints();\r
+    setProperties();\r
+\r
+    configureSwagger();\r
+  }\r
+\r
+\r
+  private void registerFeatures() {\r
+    register(MultiPartFeature.class);\r
+    register(new OTFLoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));\r
+\r
+  }\r
+\r
+  private void registerEndpoints() {\r
+    register(TestInstanceServiceImpl.class);\r
+    register(HealthServiceImpl.class);\r
+    register(TestStrategyServiceImpl.class);\r
+    register(TestExecutionServiceImpl.class);\r
+    register(VirtualTestHeadServiceImpl.class);\r
+\r
+    register(OtfOpenServiceImpl.class);\r
+  }\r
+\r
+  private void setProperties() {\r
+    property(ServletProperties.FILTER_FORWARD_ON_404, true);\r
+    property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);\r
+  }\r
+\r
+  private void configureSwagger() {\r
+    OpenApiResource openApiResource = new OpenApiResource();\r
+\r
+    register(openApiResource);\r
+  }\r
+\r
+  @Bean\r
+  @Primary\r
+  public ObjectMapper objectMapper() {\r
+    ObjectMapper objectMapper = new ObjectMapper();\r
+    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);\r
+    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);\r
+    objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);\r
+    objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);\r
+    return objectMapper;\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilter.java b/otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilter.java
new file mode 100644 (file)
index 0000000..aba17f0
--- /dev/null
@@ -0,0 +1,134 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import com.google.common.base.Strings;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.TreeMap;\r
+import javax.servlet.Filter;\r
+import javax.servlet.FilterChain;\r
+import javax.servlet.FilterConfig;\r
+import javax.servlet.ServletException;\r
+import javax.servlet.ServletRequest;\r
+import javax.servlet.ServletResponse;\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+import org.onap.aaf.cadi.Access;\r
+import org.onap.aaf.cadi.Access.Level;\r
+import org.onap.aaf.cadi.ServletContextAccess;\r
+import org.onap.aaf.cadi.util.Split;\r
+\r
+public class OTFApiEnforcementFilter implements Filter {\r
+  private static final Log log = LogFactory.getLog(OTFApiEnforcementFilter.class);\r
+  private String type;\r
+  private Map<String, List<String>> publicPaths;\r
+  private Access access = null;\r
+\r
+  public OTFApiEnforcementFilter(Access access, String enforce) throws ServletException {\r
+    this.access = access;\r
+    init(enforce);\r
+  }\r
+\r
+  @Override\r
+  public void init(FilterConfig fc) throws ServletException {\r
+    init(fc.getInitParameter("aaf_perm_type"));\r
+    // need the Context for Logging, instantiating ClassLoader, etc\r
+    ServletContextAccess sca = new ServletContextAccess(fc);\r
+    if (access == null) {\r
+      access = sca;\r
+    }\r
+  }\r
+\r
+  private void init(final String ptypes) throws ServletException {\r
+    if (Strings.isNullOrEmpty(ptypes)) {\r
+      throw new ServletException("OTFApiEnforcement requires aaf_perm_type property");\r
+    }\r
+    String[] full = Split.splitTrim(';', ptypes);\r
+    if (full.length <= 0) {\r
+      throw new ServletException("aaf_perm_type property is empty");\r
+    }\r
+\r
+    type = full[0];\r
+    publicPaths = new TreeMap<>();\r
+    if (full.length > 1) {\r
+      for (int i = 1; i < full.length; ++i) {\r
+        String[] pubArray = Split.split(':', full[i]);\r
+        if (pubArray.length == 2) {\r
+          List<String> ls = publicPaths.get(pubArray[0]);\r
+          if (ls == null) {\r
+            ls = new ArrayList<>();\r
+            publicPaths.put(pubArray[0], ls);\r
+          }\r
+          ls.add(pubArray[1]);\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc)\r
+      throws IOException, ServletException {\r
+    HttpServletRequest hreq = (HttpServletRequest) req;\r
+    final String meth = hreq.getMethod();\r
+    String path = hreq.getContextPath(); // + hreq.getPathInfo();\r
+\r
+    if (Strings.isNullOrEmpty(path) || "null".equals(path)) {\r
+      path = hreq.getRequestURI().substring(hreq.getContextPath().length());\r
+    }\r
+\r
+    List<String> list = publicPaths.get(meth);\r
+    if (list != null) {\r
+      for (String p : publicPaths.get(meth)) {\r
+        if (path.startsWith(p)) {\r
+          access.printf(\r
+              Level.INFO,\r
+              "%s accessed public API %s %s\n",\r
+              hreq.getUserPrincipal().getName(),\r
+              meth,\r
+              path);\r
+          fc.doFilter(req, resp);\r
+          return;\r
+        }\r
+      }\r
+    }\r
+    if (hreq.isUserInRole(type + '|' + path + '|' + meth)) {\r
+      access.printf(\r
+          Level.INFO,\r
+          "%s is allowed access to %s %s\n",\r
+          hreq.getUserPrincipal().getName(),\r
+          meth,\r
+          path);\r
+      fc.doFilter(req, resp);\r
+    } else {\r
+      access.printf(\r
+          Level.AUDIT,\r
+          "%s is denied access to %s %s\n",\r
+          hreq.getUserPrincipal().getName(),\r
+          meth,\r
+          path);\r
+      ((HttpServletResponse) resp).sendError(HttpServletResponse.SC_UNAUTHORIZED);\r
+    }\r
+  }\r
+\r
+  @Override\r
+  public void destroy() {}\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilterConfiguration.java b/otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilterConfiguration.java
new file mode 100644 (file)
index 0000000..cd37067
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import javax.servlet.Filter;\r
+import javax.servlet.FilterConfig;\r
+import javax.servlet.ServletException;\r
+import org.onap.aaf.cadi.Access;\r
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\r
+import org.springframework.boot.web.servlet.FilterRegistrationBean;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Conditional;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.PropertySource;\r
+\r
+@PropertySource("classpath:application.properties")\r
+@Configuration\r
+@ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true")\r
+public class OTFApiEnforcementFilterConfiguration {\r
+\r
+  private Access access;\r
+  private FilterConfig fc;\r
+\r
+  @Bean(name = "otfApiEnforcementFilterRegistrationBean")\r
+  @ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true")\r
+  public FilterRegistrationBean<Filter> otfApiEnforcementFilterRegistration()\r
+      throws ServletException {\r
+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();\r
+    initFilterParameters(registration);\r
+    registration.addUrlPatterns("/otf/api/testInstance/*", "/otf/api/testExecution/*", "/otf/api/testStrategy/*", "/otf/api/virtualTestHead/*");\r
+    registration.setFilter(otfApiEnforcementFilter());\r
+    registration.setName("otfApiEnforcementFilter");\r
+    registration.setOrder(1);\r
+    return registration;\r
+  }\r
+\r
+  @Bean(name = "otfApiEnforcementFilter")\r
+  @ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true")\r
+  public Filter otfApiEnforcementFilter() throws ServletException {\r
+    return new OTFApiEnforcementFilter(access, System.getenv("AAF_PERM_TYPE"));\r
+  }\r
+\r
+  private void initFilterParameters(FilterRegistrationBean<Filter> registration) {\r
+    registration.addInitParameter("aaf_perm_type", System.getenv("AAF_PERM_TYPE"));\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/config/OTFLoggingFeature.java b/otf-service-api/src/main/java/org/oran/otf/api/config/OTFLoggingFeature.java
new file mode 100644 (file)
index 0000000..c13caab
--- /dev/null
@@ -0,0 +1,238 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import org.glassfish.jersey.logging.LoggingFeature;\r
+import org.glassfish.jersey.message.MessageUtils;\r
+\r
+import javax.ws.rs.WebApplicationException;\r
+import javax.ws.rs.client.ClientRequestContext;\r
+import javax.ws.rs.client.ClientRequestFilter;\r
+import javax.ws.rs.client.ClientResponseContext;\r
+import javax.ws.rs.client.ClientResponseFilter;\r
+import javax.ws.rs.container.ContainerRequestContext;\r
+import javax.ws.rs.container.ContainerRequestFilter;\r
+import javax.ws.rs.container.ContainerResponseContext;\r
+import javax.ws.rs.container.ContainerResponseFilter;\r
+import javax.ws.rs.core.FeatureContext;\r
+import javax.ws.rs.core.MultivaluedMap;\r
+import javax.ws.rs.ext.WriterInterceptor;\r
+import javax.ws.rs.ext.WriterInterceptorContext;\r
+import java.io.*;\r
+import java.net.URI;\r
+import java.nio.charset.Charset;\r
+import java.util.ArrayList;\r
+import java.util.Base64;\r
+import java.util.List;\r
+import java.util.Objects;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+\r
+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,\r
+        ClientRequestFilter, ClientResponseFilter, WriterInterceptor {\r
+\r
+    private static final boolean printEntity = true;\r
+    private static final int maxEntitySize = 8 * 1024;\r
+    private final Logger logger = Logger.getLogger("OTFLoggingFeature");\r
+    private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();\r
+    private static final String NOTIFICATION_PREFIX = "* ";\r
+    private static final String REQUEST_PREFIX = "> ";\r
+    private static final String RESPONSE_PREFIX = "< ";\r
+    private static final String AUTHORIZATION = "Authorization";\r
+    private static final String EQUAL = " = ";\r
+    private static final String HEADERS_SEPARATOR = ", ";\r
+    private static List<String> requestHeaders;\r
+\r
+    static {\r
+        requestHeaders = new ArrayList<>();\r
+        requestHeaders.add(AUTHORIZATION);\r
+    }\r
+\r
+    public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {\r
+        super(logger, level, verbosity, maxEntitySize);\r
+    }\r
+\r
+    @Override\r
+    public boolean configure(FeatureContext context) {\r
+        context.register(this);\r
+        return true;\r
+    }\r
+\r
+    private Object getEmail(Object authorization){\r
+        try{\r
+            String encoded = ((String) authorization).split(" ")[1];\r
+            String decoded =  new String(Base64.getDecoder().decode(encoded));\r
+            return decoded.split(":")[0];\r
+        }\r
+        catch (Exception e){\r
+            return authorization;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ClientRequestContext context) {\r
+        final StringBuilder b = new StringBuilder();\r
+        printHeaders(b, context.getStringHeaders());\r
+        printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());\r
+\r
+        if (printEntity && context.hasEntity()) {\r
+            final OutputStream stream = new LoggingStream(b, context.getEntityStream());\r
+            context.setEntityStream(stream);\r
+            context.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+            // not calling log(b) here - it will be called by the interceptor\r
+        } else {\r
+            log(b);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {\r
+        final StringBuilder b = new StringBuilder();\r
+        printResponseLine(b, "Client response received", responseContext.getStatus());\r
+\r
+        if (printEntity && responseContext.hasEntity()) {\r
+            responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),\r
+                    MessageUtils.getCharset(responseContext.getMediaType())));\r
+        }\r
+        log(b);\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ContainerRequestContext context) throws IOException {\r
+        final StringBuilder b = new StringBuilder();\r
+        printHeaders(b, context.getHeaders());\r
+        printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());\r
+\r
+        if (printEntity && context.hasEntity()) {\r
+            context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));\r
+        }\r
+        log(b);\r
+    }\r
+\r
+    @Override\r
+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {\r
+        final StringBuilder b = new StringBuilder();\r
+        printResponseLine(b, "Server responded with a response", responseContext.getStatus());\r
+\r
+        if (printEntity && responseContext.hasEntity()) {\r
+            final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());\r
+            responseContext.setEntityStream(stream);\r
+            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+            // not calling log(b) here - it will be called by the interceptor\r
+        } else {\r
+            log(b);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {\r
+        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);\r
+        writerInterceptorContext.proceed();\r
+        if (stream != null) {\r
+            log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));\r
+        }\r
+    }\r
+\r
+    private static class LoggingStream extends FilterOutputStream {\r
+        private final StringBuilder b;\r
+        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\r
+\r
+        LoggingStream(final StringBuilder b, final OutputStream inner) {\r
+            super(inner);\r
+\r
+            this.b = b;\r
+        }\r
+\r
+        StringBuilder getStringBuilder(Charset charset) {\r
+            // write entity to the builder\r
+            final byte[] entity = byteArrayOutputStream.toByteArray();\r
+\r
+            b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));\r
+            if (entity.length > maxEntitySize) {\r
+                b.append("...more...");\r
+            }\r
+            b.append('\n');\r
+\r
+            return b;\r
+        }\r
+\r
+        public void write(final int i) throws IOException {\r
+            if (byteArrayOutputStream.size() <= maxEntitySize) {\r
+                byteArrayOutputStream.write(i);\r
+            }\r
+            out.write(i);\r
+        }\r
+    }\r
+\r
+    private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {\r
+        for (String header : requestHeaders) {\r
+            if (Objects.nonNull(headers.get(header))) {\r
+                if(header.equalsIgnoreCase("Authorization")){\r
+                    b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);\r
+                }\r
+                else{\r
+                    b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);\r
+                }\r
+            }\r
+        }\r
+        int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);\r
+        if (lastIndex != -1) {\r
+            b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());\r
+            b.append("\n");\r
+        }\r
+    }\r
+\r
+    private void log(final StringBuilder b) {\r
+        String message = b.toString();\r
+        if (logger != null) {\r
+            logger.info(message);\r
+        }\r
+    }\r
+\r
+    private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {\r
+        b.append(NOTIFICATION_PREFIX)\r
+                .append(note)\r
+                .append(" on thread ").append(Thread.currentThread().getId())\r
+                .append(REQUEST_PREFIX).append(method).append(" ")\r
+                .append(uri.toASCIIString()).append("\n");\r
+    }\r
+\r
+    private void printResponseLine(final StringBuilder b, final String note, final int status) {\r
+        b.append(NOTIFICATION_PREFIX)\r
+                .append(note)\r
+                .append(" on thread ").append(Thread.currentThread().getId())\r
+                .append(RESPONSE_PREFIX)\r
+                .append(Integer.toString(status))\r
+                .append("\n");\r
+    }\r
+\r
+    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {\r
+        if (!stream.markSupported()) {\r
+            stream = new BufferedInputStream(stream);\r
+        }\r
+        stream.mark(maxEntitySize + 1);\r
+        final byte[] entity = new byte[maxEntitySize + 1];\r
+        final int entitySize = stream.read(entity);\r
+        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));\r
+        if (entitySize > maxEntitySize) {\r
+            b.append("...more...");\r
+        }\r
+        b.append('\n');\r
+        stream.reset();\r
+        return stream;\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/exception/TestHeadNotFoundException.java b/otf-service-api/src/main/java/org/oran/otf/api/exception/TestHeadNotFoundException.java
new file mode 100644 (file)
index 0000000..7132a88
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.exception;\r
+\r
+public class TestHeadNotFoundException extends Exception {\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  public TestHeadNotFoundException(String message) {\r
+    super(message);\r
+  }\r
+\r
+  public TestHeadNotFoundException(Throwable cause) {\r
+    super(cause);\r
+  }\r
+\r
+  public TestHeadNotFoundException(String message, Throwable cause) {\r
+    super(message, cause);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/exception/TestParametersException.java b/otf-service-api/src/main/java/org/oran/otf/api/exception/TestParametersException.java
new file mode 100644 (file)
index 0000000..2029f5c
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.exception;\r
+\r
+public class TestParametersException extends Exception {\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  public TestParametersException(String message) {\r
+    super(message);\r
+  }\r
+\r
+  public TestParametersException(Throwable cause) {\r
+    super(cause);\r
+  }\r
+\r
+  public TestParametersException(String message, Throwable cause) {\r
+    super(message, cause);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/exception/UserNotFoundException.java b/otf-service-api/src/main/java/org/oran/otf/api/exception/UserNotFoundException.java
new file mode 100644 (file)
index 0000000..d319bcb
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.exception;\r
+\r
+public class UserNotFoundException extends Exception {\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  public UserNotFoundException(String message) {\r
+    super(message);\r
+  }\r
+\r
+  public UserNotFoundException(Throwable cause) {\r
+    super(cause);\r
+  }\r
+\r
+  public UserNotFoundException(String message, Throwable cause) {\r
+    super(message, cause);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessDeploymentHandler.java b/otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessDeploymentHandler.java
new file mode 100644 (file)
index 0000000..25c06b3
--- /dev/null
@@ -0,0 +1,131 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.handler;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import java.io.InputStream;\r
+import java.util.Base64;\r
+import javax.ws.rs.core.Response;\r
+import org.apache.http.HttpEntity;\r
+import org.apache.http.client.ClientProtocolException;\r
+import org.apache.http.client.ResponseHandler;\r
+import org.apache.http.client.methods.HttpUriRequest;\r
+import org.apache.http.client.methods.RequestBuilder;\r
+import org.apache.http.conn.HttpHostConnectException;\r
+import org.apache.http.conn.ssl.NoopHostnameVerifier;\r
+import org.apache.http.entity.ContentType;\r
+import org.apache.http.entity.mime.MultipartEntityBuilder;\r
+import org.apache.http.impl.client.CloseableHttpClient;\r
+import org.apache.http.impl.client.HttpClients;\r
+import org.apache.http.util.EntityUtils;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class CamundaProcessDeploymentHandler {\r
+  private static final Logger logger =\r
+      LoggerFactory.getLogger(CamundaProcessDeploymentHandler.class);\r
+\r
+  private CamundaProcessDeploymentHandler() {\r
+    // prevent instantiation\r
+  }\r
+\r
+  public Response start(InputStream bpmn, InputStream compressedResources) {\r
+    // Read necessary environment variables - Avoiding using Spring dependencies (@Value)\r
+    String host = System.getenv("otf.camunda.host");\r
+    String path = System.getenv("otf.camunda.uri.deploy-test-strategy-zip");\r
+    int port = Utilities.TryGetEnvironmentVariable("otf.camunda.port");\r
+    String aafCredentialsDecoded =\r
+        System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");\r
+\r
+    if (!Utilities.isHostValid(host)) {\r
+      logger.error("Host (%s) must use either the http or https protocol.", host);\r
+      return null;\r
+    }\r
+\r
+    if (!Utilities.isPortValid(port)) {\r
+      logger.error(\r
+          "Invalid port (%s) specified as environment variable 'otf.camunda.port'.",\r
+          System.getenv("otf.camunda.port"));\r
+      return null;\r
+    }\r
+\r
+    // Form the full url\r
+    String postUrl = String.format("%s:%s/%s", host, port, path);\r
+\r
+    try (CloseableHttpClient httpclient =\r
+        HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build()) {\r
+\r
+      // build multipart upload request\r
+      MultipartEntityBuilder builder =\r
+          MultipartEntityBuilder.create()\r
+              .addBinaryBody("bpmn", bpmn, ContentType.DEFAULT_BINARY, "bpmn");\r
+\r
+      // add resources to the request if they were supplied\r
+      if (compressedResources != null) {\r
+        builder.addBinaryBody(\r
+            "resources", compressedResources, ContentType.DEFAULT_BINARY, "resources");\r
+      }\r
+\r
+      HttpEntity data = builder.build();\r
+\r
+      // build http request and assign multipart upload data\r
+      HttpUriRequest request =\r
+          RequestBuilder.post(postUrl)\r
+              .addHeader(\r
+                  "Authorization",\r
+                  "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()))\r
+              .setEntity(data)\r
+              .build();\r
+\r
+      System.out.println("Executing request " + request.getRequestLine());\r
+\r
+      // Create a custom response handler\r
+      ResponseHandler<Response> responseHandler =\r
+          response -> {\r
+            int status = response.getStatusLine().getStatusCode();\r
+            if (status >= 200 && status < 300) {\r
+              HttpEntity entity = response.getEntity();\r
+              String message = entity != null ? EntityUtils.toString(entity) : null;\r
+              return Response.ok(message).build();\r
+            } else if (status == 400) {\r
+              HttpEntity entity = response.getEntity();\r
+              String message =\r
+                  entity != null\r
+                      ? EntityUtils.toString(entity)\r
+                      : "Supplied bpmn file is not deployable.";\r
+              return Utilities.Http.BuildResponse.badRequestWithMessage(message);\r
+            } else {\r
+              throw new ClientProtocolException("Unexpected response status: " + status);\r
+            }\r
+          };\r
+\r
+      Response responseBody = httpclient.execute(request, responseHandler);\r
+      System.out.println("----------------------------------------");\r
+      System.out.println(responseBody.getEntity().toString());\r
+\r
+      return responseBody;\r
+    } catch (HttpHostConnectException e) {\r
+      return ResponseUtility.Build.serviceUnavailableWithMessage(e.getMessage());\r
+    } catch (Exception e) {\r
+      e.printStackTrace();\r
+      return ResponseUtility.Build.internalServerErrorWithMessage("Unable to deploy definition.");\r
+    }\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessExecutionHandler.java b/otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessExecutionHandler.java
new file mode 100644 (file)
index 0000000..00e26d7
--- /dev/null
@@ -0,0 +1,105 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.handler;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.api.Utilities.LogLevel;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import org.oran.otf.common.model.local.WorkflowRequest;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import com.fasterxml.jackson.core.type.TypeReference;\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+import org.apache.http.HttpEntity;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.conn.HttpHostConnectException;\r
+import org.apache.http.util.EntityUtils;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class CamundaProcessExecutionHandler {\r
+  private static final Logger logger =\r
+      LoggerFactory.getLogger(CamundaProcessExecutionHandler.class);\r
+\r
+  private CamundaProcessExecutionHandler() {\r
+    // prevent instantiation\r
+  }\r
+\r
+  public Response startProcessInstance(WorkflowRequest request) throws Exception {\r
+    try {\r
+      //      if (!Utilities.Camunda.isCamundaOnline()) {\r
+      //        Utilities.Http.BuildResponse.internalServerErrorWithMessage(\r
+      //            "Unable to start process instance because the test control unit is\r
+      // unavailable.");\r
+      //      }\r
+\r
+      // Read necessary environment variables - Avoiding using Spring dependencies (@Value)\r
+      String host = System.getenv("otf.camunda.host");\r
+      String path = System.getenv("otf.camunda.uri.execute-test");\r
+      int port = Utilities.TryGetEnvironmentVariable("otf.camunda.port");\r
+\r
+      if (!Utilities.isHostValid(host)) {\r
+        logger.error(String.format("Host (%s) must use either the http or https protocol.", host));\r
+        return null;\r
+      }\r
+\r
+      if (!Utilities.isPortValid(port)) {\r
+        logger.error(\r
+            String.format(\r
+                "Invalid port (%s) specified as environment variable 'otf.camunda.port'.",\r
+                System.getenv("otf.camunda.port")));\r
+        return null;\r
+      }\r
+\r
+      // Form the URL\r
+      String postUrl = String.format("%s:%s/%s", host, port, path);\r
+\r
+      // Send and store the response\r
+      HttpResponse response = Utilities.Http.httpPostJsonUsingAAF(postUrl, request.toString());\r
+      // Get the entity and attempt to convert it to a TestExecution object.\r
+      HttpEntity entity = response.getEntity();\r
+      String rawEntity = EntityUtils.toString(entity);\r
+      ObjectMapper mapper = new ObjectMapper();\r
+      OTFApiResponse otfApiResponse = mapper.readValue(rawEntity, OTFApiResponse.class);\r
+\r
+      if (otfApiResponse.getStatusCode() == 400) {\r
+        return Response.status(400)\r
+            .type(MediaType.APPLICATION_JSON_TYPE)\r
+            .entity(otfApiResponse.toString())\r
+            .build();\r
+      }\r
+\r
+      String jsonMessage = otfApiResponse.getMessage();\r
+      TestExecution testExecution =\r
+          Convert.jsonToObject(jsonMessage, new TypeReference<TestExecution>() {});\r
+      return Response.status(otfApiResponse.getStatusCode())\r
+          .entity(testExecution.toString())\r
+          .build();\r
+\r
+    } catch (HttpHostConnectException e) {\r
+      return ResponseUtility.Build.serviceUnavailableWithMessage(e.getMessage());\r
+    } catch (Exception e) {\r
+      Utilities.printStackTrace(e, LogLevel.ERROR);\r
+      return ResponseUtility.Build.internalServerError();\r
+    }\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/HealthService.java b/otf-service-api/src/main/java/org/oran/otf/api/service/HealthService.java
new file mode 100644 (file)
index 0000000..4bd2378
--- /dev/null
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service;\r
+\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import io.swagger.annotations.Api;\r
+import io.swagger.v3.oas.annotations.Operation;\r
+import io.swagger.v3.oas.annotations.media.Content;\r
+import io.swagger.v3.oas.annotations.media.Schema;\r
+import io.swagger.v3.oas.annotations.responses.ApiResponse;\r
+import io.swagger.v3.oas.annotations.tags.Tag;\r
+\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+\r
+@Api\r
+@Path("/health")\r
+@Tag(name = "Health Service", description = "Query the availability of the API")\r
+@Produces({MediaType.APPLICATION_JSON})\r
+public interface HealthService {\r
+\r
+  @GET\r
+  @Path("/v1")\r
+  @Produces({MediaType.APPLICATION_JSON})\r
+  @Operation(\r
+      summary = "Checks if the test control unit is available",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "200",\r
+            description = "The test control unit is available",\r
+            content =\r
+                @Content(\r
+                    mediaType = "application/json",\r
+                    schema = @Schema(implementation = OTFApiResponse.class)))\r
+      })\r
+  Response getHealth();\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/OtfOpenService.java b/otf-service-api/src/main/java/org/oran/otf/api/service/OtfOpenService.java
new file mode 100644 (file)
index 0000000..ec32e47
--- /dev/null
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service;\r
+\r
+import io.swagger.v3.oas.annotations.Hidden;\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Hidden\r
+@Path("/")\r
+public interface OtfOpenService {\r
+  @GET\r
+  @Hidden\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/demo/openapi.json")\r
+  Response convertSwaggerFile();\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/TestExecutionService.java b/otf-service-api/src/main/java/org/oran/otf/api/service/TestExecutionService.java
new file mode 100644 (file)
index 0000000..b1d5d5e
--- /dev/null
@@ -0,0 +1,92 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import io.swagger.annotations.Api;\r
+import io.swagger.v3.oas.annotations.Operation;\r
+import io.swagger.v3.oas.annotations.media.Content;\r
+import io.swagger.v3.oas.annotations.media.Schema;\r
+import io.swagger.v3.oas.annotations.responses.ApiResponse;\r
+import io.swagger.v3.oas.annotations.tags.Tag;\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.HeaderParam;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+@Api\r
+@Path("/testExecution")\r
+@Tag(name = "Test Services", description = "")\r
+@Produces(MediaType.APPLICATION_JSON)\r
+public interface TestExecutionService {\r
+  @GET\r
+  @Path("v1/status/executionId/{executionId}")\r
+  @Produces({MediaType.APPLICATION_JSON})\r
+  @Operation(\r
+      description = "Respond with a test execution object if it exists",\r
+      summary = "Find test execution log by processInstanceId",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "200",\r
+            description = "The created Test Instance object is returned when it is created",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = TestExecution.class))\r
+            })\r
+      })\r
+  Response getExecutionStatus(\r
+      @HeaderParam("Authorization") String authorization,\r
+      @PathParam("executionId") String executionId);\r
+\r
+  @GET\r
+  @Path("v1/executionId/{executionId}")\r
+  @Produces({MediaType.APPLICATION_JSON})\r
+  @Operation(\r
+      description =\r
+          "Respond with a test execution object, and state of the process instance if it exists.",\r
+      summary = "Find test execution log by executionId",\r
+      responses = {\r
+          @ApiResponse(\r
+              responseCode = "200",\r
+              description = "The created Test Instance object is returned when it is created",\r
+              content = {\r
+                  @Content(\r
+                      mediaType = "application/json",\r
+                      schema = @Schema(implementation = TestExecution.class))\r
+              }),\r
+          @ApiResponse(\r
+              responseCode = "404",\r
+              description =\r
+                  "No process instance was found with the executionId used to query the service",\r
+              content = {\r
+                  @Content(\r
+                      mediaType = "application/json",\r
+                      schema = @Schema(implementation = OTFApiResponse.class))\r
+              })\r
+      })\r
+  Response getExecution(\r
+      @HeaderParam("Authorization") String authorization,\r
+      @PathParam("executionId") String executionId);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/TestInstanceService.java b/otf-service-api/src/main/java/org/oran/otf/api/service/TestInstanceService.java
new file mode 100644 (file)
index 0000000..6b60801
--- /dev/null
@@ -0,0 +1,374 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import org.oran.otf.common.model.local.TestInstanceCreateRequest;\r
+import org.oran.otf.common.model.local.WorkflowRequest;\r
+import io.swagger.v3.oas.annotations.Operation;\r
+import io.swagger.v3.oas.annotations.Parameter;\r
+import io.swagger.v3.oas.annotations.media.ArraySchema;\r
+import io.swagger.v3.oas.annotations.media.Content;\r
+import io.swagger.v3.oas.annotations.media.Schema;\r
+import io.swagger.v3.oas.annotations.responses.ApiResponse;\r
+import io.swagger.v3.oas.annotations.tags.Tag;\r
+import javax.ws.rs.Consumes;\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.HeaderParam;\r
+import javax.ws.rs.POST;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+@Path("/testInstance")\r
+@Tag(name = "Test Services", description = "")\r
+@Produces(MediaType.APPLICATION_JSON)\r
+public interface TestInstanceService {\r
+  @POST\r
+  @Path("/execute/v1/id/{testInstanceId}")\r
+  @Operation(\r
+      description =\r
+          "Execute a test instance by it's unique identifier. Test instances can be executed"\r
+              + " either both synchronously and asynchronously.",\r
+      summary = "Execute test instance by id",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "200",\r
+            description =\r
+                "A successful synchronously executed test returns a test execution object",\r
+            content =\r
+                @Content(\r
+                    mediaType = "application/json",\r
+                    schema = @Schema(implementation = TestExecution.class))),\r
+        @ApiResponse(\r
+            responseCode = "201",\r
+            description =\r
+                "A successful asynchronously executed test instance returns a base test execution.",\r
+            content =\r
+                @Content(\r
+                    mediaType = "application/json",\r
+                    schema = @Schema(implementation = TestExecution.class))),\r
+        @ApiResponse(\r
+            responseCode = "401",\r
+            description =\r
+                "The mechanized identifier used with the request is prohibited from accessing the resource.",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = OTFApiResponse.class))\r
+            })\r
+      })\r
+  @Consumes(MediaType.APPLICATION_JSON)\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  Response execute(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "A string representation of a BSON ObjectId",\r
+              example = "12345678912345678912345f",\r
+              required = true,\r
+              schema =\r
+                  @Schema(\r
+                      type = "string",\r
+                      format = "objectid",\r
+                      description = "The UUID of the test instance"))\r
+          @PathParam("testInstanceId")\r
+          String testInstanceId,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization,\r
+      WorkflowRequest request);\r
+\r
+  @POST\r
+  @Operation(\r
+      description = "Create a test instance using the latest version of the test definition.",\r
+      summary = "Create test instance by test definition id",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "201",\r
+            description = "The created Test Instance object is returned when it is created",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = TestInstance.class))\r
+            })\r
+      })\r
+  @Consumes(MediaType.APPLICATION_JSON)\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/create/v1/testDefinitionId/{testDefinitionId}")\r
+  Response createByTestDefinitionId(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "A string representation of a BSON ObjectId",\r
+              example = "12345678912345678912345f",\r
+              required = true,\r
+              schema =\r
+                  @Schema(\r
+                      type = "string",\r
+                      format = "uuid",\r
+                      description = "The UUID of the test definition"))\r
+          @PathParam("testDefinitionId")\r
+          String testDefinitionId,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization,\r
+      TestInstanceCreateRequest request);\r
+\r
+  @POST\r
+  @Operation(\r
+      description = "Create a test instance using the specified version of the test definition",\r
+      summary = "Create test instance by test definition id and version",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "201",\r
+            description = "The created Test Instance object is returned when it is created",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = TestInstance.class))\r
+            })\r
+      })\r
+  @Consumes(MediaType.APPLICATION_JSON)\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/create/v1/testDefinitionId/{testDefinitionId}/version/{version}")\r
+  Response createByTestDefinitionId(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "A string representation of a BSON ObjectId",\r
+              example = "12345678912345678912345f",\r
+              required = true,\r
+              schema =\r
+                  @Schema(\r
+                      type = "string",\r
+                      format = "uuid",\r
+                      description = "The UUID of the test definition."))\r
+          @PathParam("testDefinitionId")\r
+          String testDefinitionId,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The version of the test definition used to create the instance",\r
+              example = "2",\r
+              required = true)\r
+          @PathParam("version")\r
+          int version,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization,\r
+      TestInstanceCreateRequest request);\r
+\r
+  @POST\r
+  @Operation(\r
+      description = "Create a test instance using the latest version of the test definition",\r
+      summary = "Create test instance by process definition key",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "201",\r
+            description = "The created Test Instance object is returned when it is created",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = TestInstance.class))\r
+            })\r
+      })\r
+  @Consumes(MediaType.APPLICATION_JSON)\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/create/v1/processDefinitionKey/{processDefinitionKey}")\r
+  Response createByProcessDefinitionKey(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The process definition key associated with the test definition",\r
+              example = "someUniqueProcessDefinitionKey",\r
+              required = true)\r
+          @PathParam("processDefinitionKey")\r
+          String processDefinitionKey,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization,\r
+      TestInstanceCreateRequest request);\r
+\r
+  @POST\r
+  @Operation(\r
+      description = "Create a test instance using the unique process definition key and version",\r
+      summary = "Create test instance by process definition key and version",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "201",\r
+            description = "The created Test Instance object is returned when it is created",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = TestInstance.class))\r
+            })\r
+      })\r
+  @Consumes(MediaType.APPLICATION_JSON)\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/create/v1/processDefinitionKey/{processDefinitionKey}/version/{version}")\r
+  Response createByProcessDefinitionKey(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The process definition key associated with the test definition",\r
+              example = "someUniqueProcessDefinitionKey",\r
+              required = true)\r
+          @PathParam("processDefinitionKey")\r
+          String processDefinitionKey,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The version of the test definition used to create the instance",\r
+              example = "2",\r
+              required = true)\r
+          @PathParam("version")\r
+          int version,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization,\r
+      TestInstanceCreateRequest request);\r
+\r
+  @GET\r
+  @Operation(\r
+      description = "Finds a test instance by it's unique identifier",\r
+      summary = "Find test instance by id",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "200",\r
+            description = "A Test Instance object is returned if it exists",\r
+            content = {\r
+              @Content(\r
+                  mediaType = "application/json",\r
+                  schema = @Schema(implementation = TestInstance.class))\r
+            })\r
+      })\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/v1/id/{id}")\r
+  Response findById(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "A string representation of a BSON ObjectId",\r
+              example = "12345678912345678912345f",\r
+              required = true,\r
+              schema =\r
+                  @Schema(\r
+                      type = "string",\r
+                      format = "uuid",\r
+                      description = "The UUID of the test instance"))\r
+          @PathParam("id")\r
+          String testInstanceId,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization);\r
+\r
+  @GET\r
+  @Operation(\r
+      description = "Finds all test instance belonging to the unique process definition key",\r
+      summary = "Find test instance(s) by process definition key",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "200",\r
+            description = "An array of found Test Instance objects are returned",\r
+            content = {\r
+              @Content(\r
+                  array = @ArraySchema(schema = @Schema(implementation = TestInstance.class)),\r
+                  mediaType = "application/json")\r
+            })\r
+      })\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/v1/processDefinitionKey/{processDefinitionKey}")\r
+  Response findByProcessDefinitionKey(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The process definition key associated with the test definition",\r
+              example = "someUniqueProcessDefinitionKey",\r
+              required = true)\r
+          @PathParam("processDefinitionKey")\r
+          String processDefinitionKey,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization);\r
+\r
+  @GET\r
+  @Operation(\r
+      description =\r
+          "Finds all test instance belonging to the unique process definition key and version",\r
+      summary = "Find test instance(s) by process definition key and version",\r
+      responses = {\r
+        @ApiResponse(\r
+            responseCode = "200",\r
+            description = "An array of found Test Instance objects are returned",\r
+            content = {\r
+              @Content(\r
+                  array = @ArraySchema(schema = @Schema(implementation = TestInstance.class)),\r
+                  mediaType = "application/json")\r
+            })\r
+      })\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  @Path("/v1/processDefinitionKey/{processDefinitionKey}/version/{version}")\r
+  Response findByProcessDefinitionKeyAndVersion(\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The process definition key associated with the test definition",\r
+              example = "someUniqueProcessDefinitionKey",\r
+              required = true)\r
+          @PathParam("processDefinitionKey")\r
+          String processDefinitionKey,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "The version of the test definition used to create the instance",\r
+              example = "2",\r
+              required = true)\r
+          @PathParam("version")\r
+          String version,\r
+      @Parameter(\r
+              allowEmptyValue = false,\r
+              description = "Base64 encoded Application Authorization Framework credentials",\r
+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",\r
+              required = true)\r
+          @HeaderParam("Authorization")\r
+          String authorization);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/TestStrategyService.java b/otf-service-api/src/main/java/org/oran/otf/api/service/TestStrategyService.java
new file mode 100644 (file)
index 0000000..84b2535
--- /dev/null
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service;\r
+\r
+import io.swagger.annotations.Api;\r
+import io.swagger.v3.oas.annotations.Hidden;\r
+import io.swagger.v3.oas.annotations.tags.Tag;\r
+import java.io.InputStream;\r
+import javax.ws.rs.Consumes;\r
+import javax.ws.rs.DELETE;\r
+import javax.ws.rs.HeaderParam;\r
+import javax.ws.rs.POST;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.PathParam;\r
+import javax.ws.rs.Produces;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+import org.glassfish.jersey.media.multipart.FormDataParam;\r
+\r
+@Api\r
+@Hidden\r
+@Path("/testStrategy")\r
+@Tag(name = "Test Service", description = "Deploy and delete test strategies to and from the test control unit. (This documentation will only be available to the development team)")\r
+@Produces({MediaType.APPLICATION_JSON})\r
+public interface TestStrategyService {\r
+  @POST\r
+  @Hidden\r
+  @Path("/deploy/v1")\r
+  @Consumes(MediaType.MULTIPART_FORM_DATA)\r
+  @Produces(MediaType.APPLICATION_JSON)\r
+  Response deployTestStrategy(\r
+      @FormDataParam("bpmn") InputStream bpmn,\r
+      @FormDataParam("resources") InputStream compressedResources,\r
+      @FormDataParam("testDefinitionId") String testDefinitionId,\r
+      @FormDataParam("testDefinitionDeployerId") String testDefinitionDeployerId,\r
+      @FormDataParam("definitionId") String definitionId,\r
+      @HeaderParam("Authorization") String authorization);\r
+\r
+  @DELETE\r
+  @Hidden\r
+  @Path("/delete/v1/testDefinitionId/{testDefinitionId}")\r
+  Response deleteByTestDefinitionId(\r
+      @PathParam("testDefinitionId") String testDefinitionId,\r
+      @HeaderParam("authorization") String authorization);\r
+\r
+  @DELETE\r
+  @Hidden\r
+  @Path("/delete/v1/deploymentId/{deploymentId}")\r
+  Response deleteByDeploymentId(\r
+      @PathParam("deploymentId") String deploymentId,\r
+      @HeaderParam("authorization") String authorization);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/VirtualTestHeadService.java b/otf-service-api/src/main/java/org/oran/otf/api/service/VirtualTestHeadService.java
new file mode 100644 (file)
index 0000000..9c6ed6f
--- /dev/null
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service;\r
+\r
+import org.oran.otf.common.model.TestHead;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import io.swagger.annotations.Api;\r
+import io.swagger.v3.oas.annotations.Operation;\r
+import io.swagger.v3.oas.annotations.media.Content;\r
+import io.swagger.v3.oas.annotations.media.Schema;\r
+import io.swagger.v3.oas.annotations.responses.ApiResponse;\r
+import io.swagger.v3.oas.annotations.tags.Tag;\r
+\r
+import javax.ws.rs.*;\r
+import javax.ws.rs.core.HttpHeaders;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+\r
+\r
+@Api\r
+@Path("/virtualTestHead")\r
+@Tag(name = "Health Service", description = "Query the availability of the API")\r
+@Produces({MediaType.APPLICATION_JSON})\r
+public interface VirtualTestHeadService {\r
+\r
+    @PATCH\r
+    @Path("/v1/{testHeadName}")\r
+    @Produces({MediaType.APPLICATION_JSON})\r
+    @Operation(\r
+            summary = "Used to update fields in the virtual test head",\r
+            responses = {\r
+                    @ApiResponse(\r
+                            responseCode = "200",\r
+                            description = "The response will include the new vth object",\r
+                            content =\r
+                            @Content(\r
+                                    mediaType = "application/json",\r
+                                    schema = @Schema(implementation = OTFApiResponse.class)))\r
+            })\r
+    Response updateVirtualTestHead(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorization, @PathParam("testHeadName") String testHeadName, TestHead newTestHead);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/impl/HealthServiceImpl.java b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/HealthServiceImpl.java
new file mode 100644 (file)
index 0000000..ed4755a
--- /dev/null
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service.impl;\r
+\r
+import org.oran.otf.api.service.HealthService;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class HealthServiceImpl implements HealthService {\r
+\r
+  public HealthServiceImpl() {}\r
+\r
+  @Override\r
+  public Response getHealth() {\r
+    return Response.ok(new OTFApiResponse(200, "UP")).build();\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/impl/OtfOpenServiceImpl.java b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/OtfOpenServiceImpl.java
new file mode 100644 (file)
index 0000000..bfff6bb
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service.impl;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.api.Utilities.LogLevel;\r
+import org.oran.otf.api.service.OtfOpenService;\r
+import com.google.gson.JsonObject;\r
+import com.google.gson.JsonParser;\r
+import javax.ws.rs.core.Response;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.util.EntityUtils;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class OtfOpenServiceImpl implements OtfOpenService {\r
+\r
+  @Override\r
+  public Response convertSwaggerFile() {\r
+    try {\r
+      HttpResponse res =\r
+          Utilities.Http.httpGetUsingAAF("https://localhost:8443/otf/api/openapi.json");\r
+      String resStr = EntityUtils.toString(res.getEntity());\r
+      JsonObject resJson = new JsonParser().parse(resStr).getAsJsonObject();\r
+      if (resJson.has("openapi")) {\r
+        resJson.addProperty("openapi", "3.0.0");\r
+        return Response.ok(resJson.toString()).build();\r
+      }\r
+    } catch (Exception e) {\r
+      Utilities.printStackTrace(e, LogLevel.ERROR);\r
+    }\r
+\r
+    return Utilities.Http.BuildResponse.internalServerError();\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestExecutionServiceImpl.java b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestExecutionServiceImpl.java
new file mode 100644 (file)
index 0000000..e758c6b
--- /dev/null
@@ -0,0 +1,160 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service.impl;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.api.service.TestExecutionService;\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+import org.oran.otf.common.repository.TestExecutionRepository;\r
+import org.oran.otf.common.repository.UserRepository;\r
+import org.oran.otf.common.utility.permissions.PermissionChecker;\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import com.google.gson.JsonElement;\r
+import com.google.gson.JsonObject;\r
+import com.google.gson.JsonParser;\r
+import java.util.Optional;\r
+import java.util.UUID;\r
+import javax.ws.rs.core.Response;\r
+\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class TestExecutionServiceImpl implements TestExecutionService {\r
+  private static final Logger logger = LoggerFactory.getLogger(TestExecutionServiceImpl.class);\r
+  @Autowired\r
+  private UserRepository userRepository;\r
+  @Autowired private TestExecutionRepository testExecutionRepository;\r
+  @Autowired private GroupRepository groupRepository;\r
+\r
+  @Override\r
+  public Response getExecutionStatus(String authorization, String executionId) {\r
+    if (authorization == null) {\r
+      return Utilities.Http.BuildResponse.unauthorizedWithMessage("Missing authorization header.");\r
+    }\r
+    // check if the executionId is a valid UUID\r
+    try {\r
+      UUID.fromString(executionId);\r
+    } catch (IllegalArgumentException e) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          "Invalid execution identifier. Expected type is UUID (v4).");\r
+    }\r
+\r
+    // try to find the test execution\r
+    Optional<TestExecution> optionalTestExecution =\r
+        testExecutionRepository.findFirstByProcessInstanceId(executionId);\r
+    TestExecution testExecution = Utilities.resolveOptional(optionalTestExecution);\r
+    if (testExecution == null) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          String.format("An execution with identifier %s was not found.", executionId));\r
+    }\r
+\r
+    // try to find the group of the test execution\r
+    String testExecutionGroupId = testExecution.getGroupId().toString();\r
+    Optional<Group> optionalGroup = groupRepository.findById(testExecutionGroupId);\r
+    Group group = Utilities.resolveOptional(optionalGroup);\r
+    if (group == null) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          String.format(\r
+              "The group (id: %s) associated with the test execution does not exist.",\r
+              testExecutionGroupId));\r
+    }\r
+\r
+    // try to find the user for the mechanizedId used to make this request\r
+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+    if (user == null) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          "No user associated with mechanized identifier used for this request.");\r
+    }\r
+    // if it doesnt have read permission then return bad request\r
+    if (!PermissionChecker.hasPermissionTo(user,group, UserPermission.Permission.READ,groupRepository)){\r
+      return Utilities.Http.BuildResponse.unauthorizedWithMessage(\r
+          "Unauthorized to view this test execution.");\r
+    }\r
+    // Used the build the final response to be returned\r
+    JsonObject res = new JsonObject();\r
+    try {\r
+      // Parsing is required to prevent Gson from escaping all the characters of the json\r
+      JsonElement testExecutionParsed = new JsonParser().parse(testExecution.toString());\r
+      res.add("testExecution", testExecutionParsed);\r
+      // Get the state of the process instance using the Camunda REST API\r
+      JsonObject procInstStatus = Utilities.Camunda.processInstanceStatus(executionId);\r
+      // Extract the state of the process instance from the JSON response\r
+      String processInstanceState =\r
+          procInstStatus.getAsJsonObject("historicProcessInstance").get("state").getAsString();\r
+      // Add the result to the final response\r
+      res.addProperty("state", processInstanceState);\r
+    } catch (NullPointerException npe) {\r
+      // In the case of a null pointer exception, make it clear that the state was not able\r
+      // to be determined using the Camunda API.\r
+      logger.error("Unable to determine the live status of the test execution.");\r
+      res.addProperty("state", "Unable to determine");\r
+    }\r
+    // Send the response\r
+    return Response.ok(res.toString()).build();\r
+  }\r
+\r
+  @Override\r
+  public Response getExecution(String authorization, String processInstanceId) {\r
+    try {\r
+      UUID.fromString(processInstanceId);\r
+    } catch (IllegalArgumentException e) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          "Invalid execution identifier. Expected type is UUID (v4).");\r
+    }\r
+\r
+    // try to find the test execution\r
+    Optional<TestExecution> optionalTestExecution =\r
+        testExecutionRepository.findFirstByProcessInstanceId(processInstanceId);\r
+    TestExecution testExecution = Utilities.resolveOptional(optionalTestExecution);\r
+    if (testExecution == null) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          String.format("An execution with identifier %s was not found.", processInstanceId));\r
+    }\r
+\r
+    // try to find the group of the test execution\r
+    String testExecutionGroupId = testExecution.getGroupId().toString();\r
+    Optional<Group> optionalGroup = groupRepository.findById(testExecutionGroupId);\r
+    Group group = Utilities.resolveOptional(optionalGroup);\r
+    if (group == null) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          String.format(\r
+              "The group (id: %s) associated with the test execution does not exist.",\r
+              testExecutionGroupId));\r
+    }\r
+\r
+    // try to find the user for the mechanizedId used to make this request\r
+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+    if (user == null) {\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          "No user associated with mechanized identifier used" + " for this request.");\r
+    }\r
+\r
+    // if it doesnt have read permission then return bad request\r
+    if (!PermissionChecker.hasPermissionTo(user,group,UserPermission.Permission.READ,groupRepository)){\r
+      return Utilities.Http.BuildResponse.unauthorizedWithMessage(\r
+          "Unauthorized to view this test execution.");\r
+    }\r
+\r
+    return Response.ok(testExecution.toString()).build();\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestInstanceServiceImpl.java b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestInstanceServiceImpl.java
new file mode 100644 (file)
index 0000000..d171206
--- /dev/null
@@ -0,0 +1,807 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service.impl;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.api.Utilities.LogLevel;\r
+import org.oran.otf.api.handler.CamundaProcessExecutionHandler;\r
+import org.oran.otf.api.service.TestInstanceService;\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.model.local.TestInstanceCreateRequest;\r
+import org.oran.otf.common.model.local.WorkflowRequest;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+import org.oran.otf.common.repository.TestDefinitionRepository;\r
+import org.oran.otf.common.repository.TestInstanceRepository;\r
+import org.oran.otf.common.repository.UserRepository;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.common.utility.database.Generic;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.common.utility.permissions.PermissionChecker;\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import com.google.common.base.Strings;\r
+import org.bson.types.ObjectId;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Service;\r
+\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+import java.util.*;\r
+\r
+@Service\r
+public class TestInstanceServiceImpl implements TestInstanceService {\r
+    @Autowired\r
+    CamundaProcessExecutionHandler camundaProcessExecutionHandler;\r
+    @Autowired\r
+    UserRepository userRepository;\r
+    @Autowired\r
+    TestInstanceRepository testInstanceRepository;\r
+    @Autowired\r
+    TestDefinitionRepository testDefinitionRepository;\r
+    @Autowired\r
+    GroupRepository groupRepository;\r
+\r
+    private static final Logger logger = LoggerFactory.getLogger(TestInstanceServiceImpl.class);\r
+    private static final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+    @Override\r
+    public Response execute(String testInstanceId, String authorization, WorkflowRequest request) {\r
+        try {\r
+            if (request == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage("Request body is null.");\r
+            }\r
+\r
+            // Check if the testInstanceId is a valid BSON ObjectI, otherwise return a bad request\r
+            // response.\r
+            if (!Utilities.isObjectIdValid(testInstanceId)) {\r
+                String error =\r
+                        String.format(\r
+                                "%sThe testInstanceId, %s, is not a valid ObjectId (BSON).",\r
+                                logPrefix, testInstanceId);\r
+                return ResponseUtility.Build.badRequestWithMessage(error);\r
+            }\r
+\r
+            // Create an ObjectId now that we know the provided String was valid.\r
+            ObjectId oiTestInstanceId = new ObjectId(testInstanceId);\r
+            // Check if the testInstance exists, otherwise return a not found response.\r
+            TestInstance testInstance = Generic.findByIdGeneric(testInstanceRepository, oiTestInstanceId);\r
+            if (testInstance == null) {\r
+                String error =\r
+                        String.format(\r
+                                "%sThe testInstance with _id, %s, was not found.", logPrefix, testInstanceId);\r
+                return ResponseUtility.Build.notFoundWithMessage(error);\r
+            }\r
+            // Check if the testDefinition exists.\r
+            TestDefinition testDefinition =\r
+                    Generic.findByIdGeneric(testDefinitionRepository, testInstance.getTestDefinitionId());\r
+            if (testDefinition == null) {\r
+                String error =\r
+                        String.format(\r
+                                "%sThe testDefinition with _id, %s, was not found.",\r
+                                logPrefix, testInstance.getTestDefinitionId().toString());\r
+                return ResponseUtility.Build.notFoundWithMessage(error);\r
+            }\r
+\r
+            // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+            // the database.\r
+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+            if (mechanizedIdUser == null) {\r
+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+                if (decodedAuth == null) {\r
+                    return ResponseUtility.Build.badRequestWithMessage(\r
+                            String.format("Unable to decode authorization header: %s", authorization));\r
+                }\r
+                String error =\r
+                        String.format(\r
+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+                return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+            }\r
+\r
+            // If the mechanizedId is not an OTF mechanizedId, check if the user is authorized to\r
+            // execute\r
+            // the test instance. This is required because the executorId only needs to be read from the\r
+            // otf-frontend component. The user/group system is not fully integrated with AAF, so this\r
+            // is\r
+            // required. A better way might be to use certificates to check identities.\r
+            Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));\r
+            // if we cant find the test instance group then we cant check the permission\r
+            if (testInstanceGroup == null) {\r
+                return ResponseUtility.Build.\r
+                        badRequestWithMessage(\r
+                                String.format("Can not find test instance group, id:%s", testInstance.getGroupId().toString()));\r
+            }\r
+            // If the mechanizedId is authorized, set the executorId in the WorkflowRequest to the\r
+            // mechanizedId's ObjectId to make sure that the executorId isn't spoofed. Only use the\r
+            // executorId sent with the request if it uses the OTF mechanizedId because we "trust" it.\r
+            if (isOtfMechanizedIdentifier(mechanizedIdUser.getEmail()) && request.getExecutorId() != null) {\r
+                mechanizedIdUser = Utilities.resolveOptional(userRepository.findById(request.getExecutorId().toString()));\r
+            } else {\r
+                request.setExecutorId(mechanizedIdUser.get_id());\r
+            }\r
+            if (!PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup, UserPermission.Permission.EXECUTE,groupRepository)) {\r
+                String error =\r
+                        String.format(\r
+                                "%sUnauthorized the execute test instance with _id, %s.",\r
+                                logPrefix, testInstanceId);\r
+                return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+            }\r
+\r
+            // Set the test instance _id after authorization.\r
+            request.setTestInstanceId(testInstance.get_id());\r
+\r
+            // Check if the test instance is disabled.\r
+            if (testInstance.isDisabled()) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("The test instance with identifier %s is disabled.", testInstanceId));\r
+            }\r
+            // Check if the test definition is disabled.\r
+            if (testDefinition.isDisabled()) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format(\r
+                                "The test definition with identifier %s is disabled.",\r
+                                testInstance.getTestDefinitionId().toString()));\r
+            }\r
+\r
+            // Send the request to Camunda.\r
+            return camundaProcessExecutionHandler.startProcessInstance(request);\r
+        } catch (Exception e) {\r
+            Utilities.printStackTrace(e, LogLevel.ERROR);\r
+            return ResponseUtility.Build.internalServerError();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Response createByTestDefinitionId(\r
+            String testDefinitionId, String authorization, TestInstanceCreateRequest request) {\r
+        try {\r
+            // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+            // the database.\r
+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+            if (mechanizedIdUser == null) {\r
+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+                if (decodedAuth == null) {\r
+                    return ResponseUtility.Build.badRequestWithMessage(\r
+                            String.format("Unable to decode authorization header: %s", authorization));\r
+                }\r
+                String error =\r
+                        String.format(\r
+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+                return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+            }\r
+\r
+            // Check if the String correctly parses as an ObjectId.\r
+            if (!Utilities.isObjectIdValid(testDefinitionId)) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format(\r
+                                "The testDefinitionId %s is not a valid BSON ObjectId.", testDefinitionId));\r
+            }\r
+            ObjectId oiTestDefintionId = new ObjectId(testDefinitionId);\r
+\r
+            // Find the testDefinition\r
+            TestDefinition testDefinition =\r
+                    Generic.findByIdGeneric(testDefinitionRepository, oiTestDefintionId);\r
+            if (testDefinition == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format("Test definition with id, %s, was not found.", testDefinitionId));\r
+            }\r
+            // Check if the mechanizedId has access to the test definition.\r
+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));\r
+            if (testDefGroup == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));\r
+            }\r
+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {\r
+//                return ResponseUtility.Build.unauthorizedWithMessage(\r
+//                        String.format(\r
+//                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",\r
+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+//            }\r
+//            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {\r
+//                return ResponseUtility.Build.unauthorizedWithMessage(\r
+//                        String.format(\r
+//                                "MechanizedId, %s, does not have write access to the group with name, %s",\r
+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+//            }\r
+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,\r
+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))\r
+            {\r
+                return ResponseUtility.Build.unauthorizedWithMessage(\r
+                        String.format(\r
+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",\r
+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+            }\r
+            // Get the latest version of the test definition to link it with the test instance\r
+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, Integer.MIN_VALUE, true);\r
+            if (bpmnInstance == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format(\r
+                                "Test definition with id, %s, does not have any versions associated with it.",\r
+                                testDefinitionId));\r
+            }\r
+\r
+            TestInstance testInstance =\r
+                    new TestInstance(\r
+                            new ObjectId(),\r
+                            request.getTestInstanceName(),\r
+                            request.getTestInstanceDescription(),\r
+                            testDefinition.getGroupId(),\r
+                            testDefinition.get_id(),\r
+                            bpmnInstance.getProcessDefinitionId(),\r
+                            request.isUseLatestTestDefinition(),\r
+                            false,\r
+                            request.isSimulationMode(),\r
+                            request.getMaxExecutionTimeInMillis(),\r
+                            request.getPfloInput(),\r
+                            new HashMap<>(),\r
+                            request.getSimulationVthInput(),\r
+                            request.getTestData(),\r
+                            request.getVthInput(),\r
+                            new Date(System.currentTimeMillis()),\r
+                            new Date(System.currentTimeMillis()),\r
+                            mechanizedIdUser.get_id(),\r
+                            mechanizedIdUser.get_id());\r
+\r
+            return Response.ok()\r
+                    .type(MediaType.APPLICATION_JSON_TYPE)\r
+                    .entity(testInstance.toString())\r
+                    .build();\r
+        } catch (Exception e) {\r
+            Utilities.printStackTrace(e, LogLevel.ERROR);\r
+            return ResponseUtility.Build.internalServerError();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Response createByTestDefinitionId(\r
+            String testDefinitionId,\r
+            int version,\r
+            String authorization,\r
+            TestInstanceCreateRequest request) {\r
+        try {\r
+            // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+            // the database.\r
+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+            if (mechanizedIdUser == null) {\r
+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+                if (decodedAuth == null) {\r
+                    return ResponseUtility.Build.badRequestWithMessage(\r
+                            String.format("Unable to decode authorization header: %s", authorization));\r
+                }\r
+                String error =\r
+                        String.format(\r
+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+                return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+            }\r
+\r
+            // Check if the String correctly parses as an ObjectId.\r
+            if (!Utilities.isObjectIdValid(testDefinitionId)) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format(\r
+                                "The testDefinitionId %s is not a valid BSON ObjectId.", testDefinitionId));\r
+            }\r
+            ObjectId oiTestDefintionId = new ObjectId(testDefinitionId);\r
+\r
+            // Find the testDefinition\r
+            TestDefinition testDefinition =\r
+                    Generic.findByIdGeneric(testDefinitionRepository, oiTestDefintionId);\r
+            if (testDefinition == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format("Test definition with id, %s, was not found.", testDefinitionId));\r
+            }\r
+            // permission checking\r
+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));\r
+            if (testDefGroup == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));\r
+            }\r
+            // if not otf email and is not authorized\r
+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {\r
+////                return ResponseUtility.Build.unauthorizedWithMessage(\r
+////                        String.format(\r
+////                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",\r
+////                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+////            }\r
+////            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {\r
+////                return ResponseUtility.Build.unauthorizedWithMessage(\r
+////                        String.format(\r
+////                                "MechanizedId, %s, does not have write access to the group with name, %s",\r
+////                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+////            }\r
+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,\r
+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))\r
+            {\r
+                return ResponseUtility.Build.unauthorizedWithMessage(\r
+                        String.format(\r
+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",\r
+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+            }\r
+            // Get the latest version of the test definition to link it with the test instance\r
+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, version, false);\r
+            if (bpmnInstance == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format(\r
+                                "Test definition with id, %s, does not have any versions associated with it.",\r
+                                testDefinitionId));\r
+            }\r
+\r
+            TestInstance testInstance =\r
+                    new TestInstance(\r
+                            new ObjectId(),\r
+                            request.getTestInstanceName(),\r
+                            request.getTestInstanceDescription(),\r
+                            testDefinition.getGroupId(),\r
+                            testDefinition.get_id(),\r
+                            bpmnInstance.getProcessDefinitionId(),\r
+                            request.isUseLatestTestDefinition(),\r
+                            false,\r
+                            request.isSimulationMode(),\r
+                            request.getMaxExecutionTimeInMillis(),\r
+                            request.getPfloInput(),\r
+                            new HashMap<>(),\r
+                            request.getSimulationVthInput(),\r
+                            request.getTestData(),\r
+                            request.getVthInput(),\r
+                            new Date(System.currentTimeMillis()),\r
+                            new Date(System.currentTimeMillis()),\r
+                            mechanizedIdUser.get_id(),\r
+                            mechanizedIdUser.get_id());\r
+\r
+            return Response.ok()\r
+                    .type(MediaType.APPLICATION_JSON_TYPE)\r
+                    .entity(testInstance.toString())\r
+                    .build();\r
+        } catch (Exception e) {\r
+            Utilities.printStackTrace(e, LogLevel.ERROR);\r
+            return ResponseUtility.Build.internalServerError();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Response createByProcessDefinitionKey(\r
+            String processDefinitionKey, String authorization, TestInstanceCreateRequest request) {\r
+        try {\r
+            // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+            // the database.\r
+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+            if (mechanizedIdUser == null) {\r
+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+                if (decodedAuth == null) {\r
+                    return ResponseUtility.Build.badRequestWithMessage(\r
+                            String.format("Unable to decode authorization header: %s", authorization));\r
+                }\r
+                String error =\r
+                        String.format(\r
+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+                return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+            }\r
+\r
+            // Check if the String correctly parses as an ObjectId.\r
+            if (Strings.isNullOrEmpty(processDefinitionKey)) {\r
+                return ResponseUtility.Build.badRequestWithMessage("The processDefinitionKey is required.");\r
+            }\r
+\r
+            // Find the testDefinition\r
+            TestDefinition testDefinition =\r
+                    testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey).orElse(null);\r
+            if (testDefinition == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format(\r
+                                "Test definition with processDefinitionKey, %s, was not found.",\r
+                                processDefinitionKey));\r
+            }\r
+\r
+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));\r
+            if (testDefGroup == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));\r
+            }\r
+            // if not otf email and is not authorized\r
+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {\r
+//                return ResponseUtility.Build.unauthorizedWithMessage(\r
+//                        String.format(\r
+//                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",\r
+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+//            }\r
+//            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {\r
+//                return ResponseUtility.Build.unauthorizedWithMessage(\r
+//                        String.format(\r
+//                                "MechanizedId, %s, does not have write access to the group with name, %s",\r
+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+//            }\r
+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,\r
+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))\r
+            {\r
+                return ResponseUtility.Build.unauthorizedWithMessage(\r
+                        String.format(\r
+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",\r
+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+            }\r
+            // Get the latest version of the test definition to link it with the test instance\r
+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, Integer.MIN_VALUE, false);\r
+            if (bpmnInstance == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format(\r
+                                "Test definition with id, %s, does not have any versions associated with it.",\r
+                                testDefinition.get_id().toString()));\r
+            }\r
+\r
+            TestInstance testInstance =\r
+                    new TestInstance(\r
+                            new ObjectId(),\r
+                            request.getTestInstanceName(),\r
+                            request.getTestInstanceDescription(),\r
+                            testDefinition.getGroupId(),\r
+                            testDefinition.get_id(),\r
+                            bpmnInstance.getProcessDefinitionId(),\r
+                            request.isUseLatestTestDefinition(),\r
+                            false,\r
+                            request.isSimulationMode(),\r
+                            request.getMaxExecutionTimeInMillis(),\r
+                            request.getPfloInput(),\r
+                            new HashMap<>(),\r
+                            request.getSimulationVthInput(),\r
+                            request.getTestData(),\r
+                            request.getVthInput(),\r
+                            new Date(System.currentTimeMillis()),\r
+                            new Date(System.currentTimeMillis()),\r
+                            mechanizedIdUser.get_id(),\r
+                            mechanizedIdUser.get_id());\r
+\r
+            return Response.ok()\r
+                    .type(MediaType.APPLICATION_JSON_TYPE)\r
+                    .entity(testInstance.toString())\r
+                    .build();\r
+        } catch (Exception e) {\r
+            Utilities.printStackTrace(e, LogLevel.ERROR);\r
+            return ResponseUtility.Build.internalServerError();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Response createByProcessDefinitionKey(\r
+            String processDefinitionKey,\r
+            int version,\r
+            String authorization,\r
+            TestInstanceCreateRequest request) {\r
+        try {\r
+            // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+            // the database.\r
+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+            if (mechanizedIdUser == null) {\r
+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+                if (decodedAuth == null) {\r
+                    return ResponseUtility.Build.badRequestWithMessage(\r
+                            String.format("Unable to decode authorization header: %s", authorization));\r
+                }\r
+                String error =\r
+                        String.format(\r
+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+                return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+            }\r
+\r
+            // Check if the String correctly parses as an ObjectId.\r
+            if (Strings.isNullOrEmpty(processDefinitionKey)) {\r
+                return ResponseUtility.Build.badRequestWithMessage("The processDefinitionKey is required.");\r
+            }\r
+\r
+            // Find the testDefinition\r
+            TestDefinition testDefinition =\r
+                    testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey).orElse(null);\r
+            if (testDefinition == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format(\r
+                                "Test definition with processDefinitionKey, %s, was not found.",\r
+                                processDefinitionKey));\r
+            }\r
+\r
+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));\r
+            if (testDefGroup == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));\r
+            }\r
+            // if not otf email and is not authorized\r
+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {\r
+//                return ResponseUtility.Build.unauthorizedWithMessage(\r
+//                        String.format(\r
+//                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",\r
+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+//            }\r
+//            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {\r
+//                return ResponseUtility.Build.unauthorizedWithMessage(\r
+//                        String.format(\r
+//                                "MechanizedId, %s, does not have write access to the group with name, %s",\r
+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+//            }\r
+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,\r
+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))\r
+            {\r
+                return ResponseUtility.Build.unauthorizedWithMessage(\r
+                        String.format(\r
+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",\r
+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));\r
+            }\r
+            // Get the latest version of the test definition to link it with the test instance\r
+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, version, false);\r
+            if (bpmnInstance == null) {\r
+                return ResponseUtility.Build.notFoundWithMessage(\r
+                        String.format(\r
+                                "Test definition with id, %s, does not have any versions associated with it.",\r
+                                testDefinition.get_id().toString()));\r
+            }\r
+\r
+            TestInstance testInstance =\r
+                    new TestInstance(\r
+                            new ObjectId(),\r
+                            request.getTestInstanceName(),\r
+                            request.getTestInstanceDescription(),\r
+                            testDefinition.getGroupId(),\r
+                            testDefinition.get_id(),\r
+                            bpmnInstance.getProcessDefinitionId(),\r
+                            request.isUseLatestTestDefinition(),\r
+                            false,\r
+                            request.isSimulationMode(),\r
+                            request.getMaxExecutionTimeInMillis(),\r
+                            request.getPfloInput(),\r
+                            new HashMap<>(),\r
+                            request.getSimulationVthInput(),\r
+                            request.getTestData(),\r
+                            request.getVthInput(),\r
+                            new Date(System.currentTimeMillis()),\r
+                            new Date(System.currentTimeMillis()),\r
+                            mechanizedIdUser.get_id(),\r
+                            mechanizedIdUser.get_id());\r
+\r
+            return Response.ok()\r
+                    .type(MediaType.APPLICATION_JSON_TYPE)\r
+                    .entity(testInstance.toString())\r
+                    .build();\r
+        } catch (Exception e) {\r
+            Utilities.printStackTrace(e, LogLevel.ERROR);\r
+            return ResponseUtility.Build.internalServerError();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Response findById(String testInstanceId, String authorization) {\r
+        // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+        // the database.\r
+        User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+        if (mechanizedIdUser == null) {\r
+            String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+            if (decodedAuth == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Unable to decode authorization header: %s", authorization));\r
+            }\r
+            String error =\r
+                    String.format("%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+            return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+        }\r
+\r
+        // Check if the testInstanceId is a valid BSON ObjectI, otherwise return a bad request\r
+        // response.\r
+        if (!Utilities.isObjectIdValid(testInstanceId)) {\r
+            String error =\r
+                    String.format(\r
+                            "%sThe testInstanceId, %s, is not a valid ObjectId (BSON).",\r
+                            logPrefix, testInstanceId);\r
+            return ResponseUtility.Build.badRequestWithMessage(error);\r
+        }\r
+\r
+        // Create an ObjectId now that we know the provided String was valid.\r
+        ObjectId oiTestInstanceId = new ObjectId(testInstanceId);\r
+        // Check if the testInstance exists, otherwise return a not found response.\r
+        TestInstance testInstance = Generic.findByIdGeneric(testInstanceRepository, oiTestInstanceId);\r
+        if (testInstance == null) {\r
+            String error =\r
+                    String.format(\r
+                            "%sThe testInstance with _id, %s, was not found.", logPrefix, testInstanceId);\r
+            return ResponseUtility.Build.notFoundWithMessage(error);\r
+        }\r
+\r
+        Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));\r
+        if (testInstanceGroup == null) {\r
+            return ResponseUtility.Build.badRequestWithMessage(\r
+                    String.format("Can not find test instance's group, group name :%s", testInstance.get_id().toString()));\r
+        }\r
+        if (!PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup,UserPermission.Permission.READ,groupRepository)) {\r
+            return ResponseUtility.Build.unauthorizedWithMessage(\r
+                    String.format(\r
+                            "User %s does not have read access to test instance group, group name: %s.",\r
+                            mechanizedIdUser.getEmail(), testInstanceGroup.getGroupName()));\r
+        }\r
+        return Response.ok(testInstance.toString(), MediaType.APPLICATION_JSON_TYPE).build();\r
+    }\r
+\r
+    @Override\r
+    public Response findByProcessDefinitionKey(String processDefinitionKey, String authorization) {\r
+        // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+        // the database.\r
+        User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+        if (mechanizedIdUser == null) {\r
+            String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+            if (decodedAuth == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Unable to decode authorization header: %s", authorization));\r
+            }\r
+            String error =\r
+                    String.format("%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+            return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+        }\r
+\r
+        Optional<TestDefinition> optionalTestDefinition =\r
+                testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey);\r
+        TestDefinition testDefinition = optionalTestDefinition.orElse(null);\r
+        if (testDefinition == null) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format(\r
+                            "Cannot find test instance because a test"\r
+                                    + " definition with the process definition key (%s) does not exist.",\r
+                            processDefinitionKey));\r
+        }\r
+\r
+        List<TestInstance> testInstances =\r
+                testInstanceRepository.findAllByTestDefinitionId(testDefinition.get_id());\r
+        if (testInstances.isEmpty()) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format(\r
+                            "No test instances found with process " + "definition key (%s).",\r
+                            processDefinitionKey));\r
+        }\r
+\r
+        List<TestInstance> result = new ArrayList<>();\r
+        for (TestInstance testInstance : testInstances) {\r
+            Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));\r
+            if (testInstanceGroup != null && PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup,UserPermission.Permission.READ,groupRepository)) {\r
+                result.add(testInstance);\r
+            }\r
+        }\r
+\r
+        return Response.ok(result.toString()).build();\r
+    }\r
+\r
+    @Override\r
+    public Response findByProcessDefinitionKeyAndVersion(\r
+            String processDefinitionKey, String version, String authorization) {\r
+        // Check if a user associated with the mechanizedId used in the authorization header exists in\r
+        // the database.\r
+        User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+        if (mechanizedIdUser == null) {\r
+            String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);\r
+            if (decodedAuth == null) {\r
+                return ResponseUtility.Build.badRequestWithMessage(\r
+                        String.format("Unable to decode authorization header: %s", authorization));\r
+            }\r
+            String error =\r
+                    String.format("%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);\r
+            return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+        }\r
+\r
+        Optional<TestDefinition> optionalTestDefinition =\r
+                testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey);\r
+        TestDefinition testDefinition = optionalTestDefinition.orElse(null);\r
+\r
+        if (testDefinition == null) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format(\r
+                            "Cannot find test instance because a test"\r
+                                    + " definition with the process definition key (%s) does not exist.",\r
+                            processDefinitionKey));\r
+        }\r
+\r
+        int iVersion;\r
+        try {\r
+            iVersion = Integer.parseInt(version);\r
+        } catch (NumberFormatException nfe) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage("Version must be a valid integer.");\r
+        }\r
+\r
+        BpmnInstance bpmnInstance =\r
+                testDefinition.getBpmnInstances().stream()\r
+                        .filter(_bpmnInstance -> _bpmnInstance.getVersion() == iVersion)\r
+                        .findAny()\r
+                        .orElse(null);\r
+\r
+        if (bpmnInstance == null) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format("Cannot find any test instances using " + "version %s.", version));\r
+        }\r
+\r
+        List<TestInstance> testInstances =\r
+                testInstanceRepository.findAllByTestDefinitionIdAndPDId(\r
+                        testDefinition.get_id(), bpmnInstance.getProcessDefinitionId());\r
+\r
+        if (testInstances.isEmpty()) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format(\r
+                            "No test instances found with process " + "definition key (%s).",\r
+                            processDefinitionKey));\r
+        }\r
+\r
+        List<TestInstance> result = new ArrayList<>();\r
+        for (TestInstance testInstance : testInstances) {\r
+            Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));\r
+            if (testInstanceGroup != null && PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup,UserPermission.Permission.READ,groupRepository)) {\r
+                result.add(testInstance);\r
+            }\r
+        }\r
+\r
+        return Response.ok(result.toString()).build();\r
+    }\r
+\r
+    private boolean isOtfMechanizedIdentifier(String email) {\r
+        return email.equalsIgnoreCase("email@localhost")\r
+                || email.equalsIgnoreCase("email@localhost")\r
+                || email.equalsIgnoreCase("email@localhost")\r
+                || email.equalsIgnoreCase("email@localhost")\r
+                || email.equalsIgnoreCase("email@localhost");\r
+    }\r
+\r
+    private BpmnInstance findBpmnInstance(TestDefinition testDefinition, int version, boolean latest)\r
+            throws Exception {\r
+        BpmnInstance bpmnInstance = null;\r
+        int maxVersion = Integer.MIN_VALUE;\r
+        // Check if the version exists\r
+        for (BpmnInstance bi : testDefinition.getBpmnInstances()) {\r
+            // If this field is null or empty, it means the bpmn hasn't been deployed, or there was a\r
+            // creation error on the Test Definition page (UI). Skip the field so the user isn't allowed\r
+            // to create a test instance based off this bpmn instance.\r
+            if (Strings.isNullOrEmpty(bi.getProcessDefinitionId())) {\r
+                continue;\r
+            }\r
+\r
+            // Split the processDefinitionId based on it's format:\r
+            // {processDefinitionKey}:{version}:{processDefinitionId}.\r
+            String processDefinitionId = bi.getProcessDefinitionId();\r
+            String[] processDefinitionIdSplit = processDefinitionId.split(":");\r
+            if (processDefinitionIdSplit.length != 3) {\r
+                throw new Exception(\r
+                        String.format(\r
+                                "testDefinition[%s].bpmnInstances.processDefinitionId[%s] is invalid.",\r
+                                testDefinition.get_id().toString(), bi.getProcessDefinitionId()));\r
+            }\r
+\r
+            String sVersion = processDefinitionIdSplit[1];\r
+            int currentVersion = Integer.parseInt(sVersion);\r
+            if (latest && currentVersion > maxVersion) {\r
+                bpmnInstance = bi;\r
+            } else if (currentVersion == version) {\r
+                bpmnInstance = bi;\r
+                break;\r
+            }\r
+        }\r
+\r
+        return bpmnInstance;\r
+    }\r
+\r
+//    private boolean isAuthorized(User user, Group group, String permission, GroupRepository groupRepository) {\r
+//        if (isOtfMechanizedIdentifier(user.getEmail())) {\r
+//            return true;\r
+//        }\r
+//        return PermissionChecker.isAuthorized(user, group, permission.toUpperCase(), groupRepository);\r
+//    }\r
+}\r
+/*\r
+ PermissionChecker.hasReadPermission(mechanizedIdUser,testInstanceGroup,groupRepository)\r
+\r
+  PermissionChecker.hasPermission(mechanizedIdUser,testInstanceGroup,groupRepository, [READ, WRITE])\r
+  PermissionsMAp = PermissionChecker.Build.hasRead\r
+ */
\ No newline at end of file
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestStrategyServiceImpl.java b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestStrategyServiceImpl.java
new file mode 100644 (file)
index 0000000..13d125a
--- /dev/null
@@ -0,0 +1,228 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service.impl;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.api.handler.CamundaProcessDeploymentHandler;\r
+import org.oran.otf.api.service.TestStrategyService;\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.model.local.DeployTestStrategyRequest;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+import org.oran.otf.common.repository.TestDefinitionRepository;\r
+import org.oran.otf.common.repository.UserRepository;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import io.swagger.v3.oas.annotations.Hidden;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.Base64;\r
+import java.util.Optional;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.conn.HttpHostConnectException;\r
+import org.apache.http.util.EntityUtils;\r
+import org.bson.types.ObjectId;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+@Hidden\r
+public class TestStrategyServiceImpl implements TestStrategyService {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(TestStrategyServiceImpl.class);\r
+\r
+  @Autowired private TestDefinitionRepository testDefinitionRepository;\r
+\r
+  @Autowired private UserRepository userRepository;\r
+\r
+  @Autowired private CamundaProcessDeploymentHandler camundaProcessDeploymentHandler;\r
+\r
+  @Autowired private GroupRepository groupRepository;\r
+\r
+  public Response deployTestStrategy(\r
+      InputStream bpmn,\r
+      InputStream compressedResources,\r
+      String testDefinitionId,\r
+      String testDefinitionDeployerId,\r
+      String definitionId,\r
+      String authorization) {\r
+    if (bpmn == null)\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          "BPMN input stream cannot be null.");\r
+\r
+    // Decode the authorization header.\r
+    byte[] decodedAuthorization = Base64.getDecoder().decode(authorization.replace("Basic ", ""));\r
+    String credentials = new String(decodedAuthorization);\r
+    String[] credentialsArray = credentials.split(":");\r
+\r
+    /* Check if the request came from the system specified mechanized identifier. The request goes through AAF\r
+     * authorization before reaching this code, therefore, assume the headers aren't spoofed. */\r
+    if (!credentialsArray[0].equals(System.getenv("AAF_ID")))\r
+      return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+          "Unauthorized to use this service.");\r
+\r
+    // Map to a POJO model2.\r
+    ObjectId _testDefinitionDeployerId = null;\r
+    ObjectId _testDefinitionId = null;\r
+\r
+    if (testDefinitionDeployerId != null && ObjectId.isValid(testDefinitionDeployerId))\r
+      _testDefinitionDeployerId = new ObjectId(testDefinitionDeployerId);\r
+    if (testDefinitionId != null && ObjectId.isValid(testDefinitionId))\r
+      _testDefinitionId = new ObjectId(testDefinitionId);\r
+\r
+    DeployTestStrategyRequest request =\r
+        new DeployTestStrategyRequest(_testDefinitionDeployerId, _testDefinitionId, definitionId);\r
+\r
+    //         String bpmnContents = null;\r
+    //         try (final Reader reader = new InputStreamReader(bpmn)) {\r
+    //                 bpmnContents = CharStreams.toString(reader);\r
+    //                 } catch (Exception e) {\r
+    //                 e.printStackTrace();\r
+    //         }\r
+\r
+    // Check if the request actually contains a bpmn string.\r
+    //         try {\r
+    //                 if (bpmnContents == null || bpmnContents.trim().length() == 0)\r
+    //                         return Utilities.Http.BuildResponse.badRequestWithMessage("BPMN contents are null.");\r
+    //         } catch (Exception e) {\r
+    //                 logger.error(Utilities.getStackTrace(e));\r
+    //         }\r
+\r
+    // If a test definition id is supplied, the request intends to update an existing test\r
+    // definition.\r
+    if (request.getTestDefinitionId() != null) {\r
+      // Check if the test definition exists in the database.\r
+      Optional<TestDefinition> testDefinitionOptional =\r
+          testDefinitionRepository.findById(request.getTestDefinitionId().toString());\r
+\r
+      if (!testDefinitionOptional.isPresent())\r
+        return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+            String.format("Test definition (%s) was not found.", request.getTestDefinitionId()));\r
+\r
+      // Check if a user to update the definition was supplied.\r
+      if (request.getTestDefinitionDeployerId() == null)\r
+        return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+            "Must specify testDefinitionDeployerId.");\r
+\r
+      // Check if the user requesting to update the definition is the user who originally created\r
+      // the definition.\r
+      TestDefinition testDefinition = testDefinitionOptional.get();\r
+\r
+      if (!testDefinition\r
+          .getCreatedBy()\r
+          .toString()\r
+          .equals(request.getTestDefinitionDeployerId().toString()))\r
+        return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+            String.format(\r
+                "User (%s) is not authorized to update this test definition.",\r
+                request.getTestDefinitionDeployerId()));\r
+\r
+      // Check if the version to deploy already exists\r
+      for (BpmnInstance bpmnInstance : testDefinition.getBpmnInstances()) {\r
+        if (bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(request.getDefinitionId()))\r
+          return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+              String.format(\r
+                  "A deployment with the definitionId %s already exists.",\r
+                  request.getDefinitionId()));\r
+      }\r
+    }\r
+\r
+    // Make the deployment request to Camunda. Relay the response received by Camunda.\r
+    return camundaProcessDeploymentHandler.start(bpmn, compressedResources);\r
+  }\r
+\r
+  public Response deleteByDeploymentId(String deploymentId, String authorization) {\r
+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+    if (!isAuthorized(authorization)) {\r
+      return Utilities.Http.BuildResponse.unauthorized();\r
+    }\r
+\r
+    String url =\r
+        String.format(\r
+            "%s:%s/%s/%s",\r
+            System.getenv("otf.camunda.host"),\r
+            System.getenv("otf.camunda.port"),\r
+            System.getenv("otf.camunda.deploymentDeletionUri"),\r
+            deploymentId);\r
+\r
+    try {\r
+      HttpResponse res = Utilities.Http.httpDeleteAAF(url);\r
+      String resStr = EntityUtils.toString(res.getEntity());\r
+      int status = res.getStatusLine().getStatusCode();\r
+      return Response.status(status)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(status, resStr))\r
+          .build();\r
+\r
+    } catch (Exception e) {\r
+      e.printStackTrace();\r
+      return Utilities.Http.BuildResponse.internalServerError();\r
+    }\r
+  }\r
+\r
+  public Response deleteByTestDefinitionId(String testDefinitionId, String authorization) {\r
+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+    if (!isAuthorized(authorization)) {\r
+      return Utilities.Http.BuildResponse.unauthorizedWithMessage("Authorization headers not set.");\r
+    }\r
+\r
+    String url =\r
+        String.format(\r
+            "%s:%s/%s/%s",\r
+            System.getenv("otf.camunda.host"),\r
+            System.getenv("otf.camunda.port"),\r
+            System.getenv("otf.camunda.testDefinitionDeletionUri"),\r
+            testDefinitionId);\r
+\r
+    try {\r
+      HttpResponse res = Utilities.Http.httpDeleteAAF(url);\r
+      String resStr = EntityUtils.toString(res.getEntity());\r
+      int status = res.getStatusLine().getStatusCode();\r
+      return Response.status(status)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(status, resStr))\r
+          .build();\r
+    } catch (HttpHostConnectException e) {\r
+      return ResponseUtility.Build.serviceUnavailableWithMessage(e.getMessage());\r
+    } catch (Exception e) {\r
+      e.printStackTrace();\r
+      return Utilities.Http.BuildResponse.internalServerError();\r
+    }\r
+  }\r
+\r
+  private boolean isAuthorized(String authorization) {\r
+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+    return (user.getEmail().equalsIgnoreCase("email@localhost")\r
+        || user.getEmail().equalsIgnoreCase("email@localhost"));\r
+  }\r
+\r
+  private DeployTestStrategyRequest mapToDeployTestStrategyRequest(String body) {\r
+    ObjectMapper mapper = new ObjectMapper();\r
+    try {\r
+      return mapper.readValue(body, DeployTestStrategyRequest.class); // Perform the mapping\r
+    } catch (IOException e) { // Indicates an unknown request body\r
+      logger.error(e.getMessage());\r
+      return null;\r
+    }\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/api/service/impl/VirtualTestHeadServiceImpl.java b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/VirtualTestHeadServiceImpl.java
new file mode 100644 (file)
index 0000000..d2a662b
--- /dev/null
@@ -0,0 +1,164 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.service.impl;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.api.service.VirtualTestHeadService;\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.TestHead;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+import org.oran.otf.common.repository.TestHeadRepository;\r
+import org.oran.otf.common.repository.UserRepository;\r
+import org.oran.otf.common.utility.Utility;\r
+import org.oran.otf.common.utility.database.Generic;\r
+import org.oran.otf.common.utility.http.ResponseUtility;\r
+import org.oran.otf.common.utility.permissions.PermissionChecker;\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+import org.springframework.stereotype.Service;\r
+\r
+import javax.ws.rs.core.Response;\r
+import java.util.Date;\r
+import java.util.Optional;\r
+\r
+@Service\r
+public class VirtualTestHeadServiceImpl implements VirtualTestHeadService {\r
+\r
+    @Autowired\r
+    private UserRepository userRepository;\r
+    @Autowired\r
+    private GroupRepository groupRepository;\r
+\r
+    @Autowired\r
+    TestHeadRepository testHeadRepository;\r
+\r
+    @Autowired\r
+    MongoTemplate mongoTemplate;\r
+\r
+    private static final String logPrefix = Utility.getLoggerPrefix();\r
+\r
+    @Override\r
+    public Response updateVirtualTestHead(String authorization, String testHeadName, TestHead newTestHead) {\r
+        if (authorization == null) {\r
+            return Utilities.Http.BuildResponse.unauthorizedWithMessage("Missing authorization header.");\r
+        }\r
+\r
+        // try to find the test head\r
+        Optional<TestHead> optionalTestHead =\r
+                testHeadRepository.findByTestHeadName(testHeadName);\r
+        TestHead testHead = Utilities.resolveOptional(optionalTestHead);\r
+        if (testHead == null) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format("A test head with identifier %s was not found.", testHeadName));\r
+        }\r
+\r
+        // try to find the group of the test head\r
+        String testHeadGroupId = testHead.getGroupId().toString();\r
+        Group testHeadGroup = Generic.findByIdGeneric(groupRepository, testHead.getGroupId());\r
+        if (testHeadGroup == null) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    String.format(\r
+                            "The group (id: %s) associated with the test head does not exist.",\r
+                            testHeadGroupId));\r
+        }\r
+\r
+        // try to find the user for the mechanizedId used to make this request\r
+        User user = Utilities.findUserByAuthHeader(authorization, userRepository);\r
+        if (user == null) {\r
+            return Utilities.Http.BuildResponse.badRequestWithMessage(\r
+                    "No user associated with mechanized identifier used for this request.");\r
+        }\r
+\r
+        if (!PermissionChecker.hasPermissionTo(user, testHeadGroup, UserPermission.Permission.WRITE, groupRepository)) {\r
+            String error =\r
+                    String.format(\r
+                            "Unauthorized the write to test head with name, %s.",\r
+                            testHeadGroupId);\r
+            return ResponseUtility.Build.unauthorizedWithMessage(error);\r
+        }\r
+\r
+        return updateTestHeadFields(testHead, newTestHead, user);\r
+    }\r
+\r
+    private Response updateTestHeadFields(TestHead testHead, TestHead newTestHead, User user) {\r
+        Query select = Query.query(Criteria.where("_id").is(testHead.get_id()));\r
+        Update update = new Update();\r
+\r
+        if (newTestHead.getTestHeadName() != null) {\r
+            if (doesTestHeadWithNameExist(newTestHead.getTestHeadName())) {\r
+                String error =\r
+                        String.format(\r
+                                "Cant change testHeadName to %s since it already exists.",\r
+                                newTestHead.getTestHeadName());\r
+                return ResponseUtility.Build.badRequestWithMessage(error);\r
+            }\r
+            testHead.setTestHeadName(newTestHead.getTestHeadName());\r
+            update.set("testHeadName", newTestHead.getTestHeadName());\r
+        }\r
+        if (newTestHead.getTestHeadDescription() != null) {\r
+            testHead.setTestHeadDescription(newTestHead.getTestHeadDescription());\r
+            update.set("testHeadDescription", newTestHead.getTestHeadDescription());\r
+        }\r
+        if (newTestHead.getHostname() != null) {\r
+            testHead.setHostname(newTestHead.getHostname());\r
+            update.set("hostname", newTestHead.getHostname());\r
+        }\r
+        if (newTestHead.getPort() != null) {\r
+            testHead.setPort(newTestHead.getPort());\r
+            update.set("port", newTestHead.getPort());\r
+        }\r
+        if (newTestHead.getResourcePath() != null) {\r
+            testHead.setResourcePath(newTestHead.getResourcePath());\r
+            update.set("resourcePath", newTestHead.getResourcePath());\r
+        }\r
+        if (newTestHead.getAuthorizationType() != null) {\r
+            testHead.setAuthorizationType(newTestHead.getAuthorizationType());\r
+            update.set("authorizationType", newTestHead.getAuthorizationType());\r
+        }\r
+        if (newTestHead.getAuthorizationCredential() != null) {\r
+            testHead.setAuthorizationCredential(newTestHead.getAuthorizationCredential());\r
+            update.set("authorizationCredential", newTestHead.getAuthorizationCredential());\r
+        }\r
+        if (newTestHead.getAuthorizationEnabled() != null) {\r
+            testHead.setAuthorizationEnabled(newTestHead.getAuthorizationEnabled());\r
+            update.set("authorizationEnabled", newTestHead.getAuthorizationEnabled());\r
+        }\r
+        if (newTestHead.getVthInputTemplate() != null) {\r
+            testHead.setVthInputTemplate(newTestHead.getVthInputTemplate());\r
+            update.set("vthInputTemplate", newTestHead.getVthInputTemplate());\r
+        }\r
+        testHead.setUpdatedAt(new Date());\r
+        update.set("updatedAt", testHead.getUpdatedAt());\r
+        testHead.setUpdatedBy(user.get_id());\r
+        update.set("updatedBy", user.get_id());\r
+\r
+        mongoTemplate.updateFirst(select, update, "testHeads");\r
+        return ResponseUtility.Build.okRequestWithObject(testHead);\r
+    }\r
+\r
+    // check if test head exists in database by name\r
+    private boolean doesTestHeadWithNameExist(String name) {\r
+        Optional<TestHead> optionalTestHead =\r
+                testHeadRepository.findByTestHeadName(name);\r
+        return optionalTestHead.isPresent();\r
+    }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/Group.java b/otf-service-api/src/main/java/org/oran/otf/common/model/Group.java
new file mode 100644 (file)
index 0000000..9214407
--- /dev/null
@@ -0,0 +1,110 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.List;\r
+\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "groups")\r
+public class Group implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+  private String groupName;\r
+  private String groupDescription;\r
+  private List<ObjectId> mechanizedIds;\r
+  private ObjectId ownerId;\r
+  private List<Role> roles;\r
+  private List<GroupMember> members;\r
+  private ObjectId parentGroupId;\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getGroupName() {\r
+    return groupName;\r
+  }\r
+\r
+  public void setGroupName(String groupName) {\r
+    this.groupName = groupName;\r
+  }\r
+\r
+  public String getGroupDescription() {\r
+    return groupDescription;\r
+  }\r
+\r
+  public void setGroupDescription(String groupDescription) {\r
+    this.groupDescription = groupDescription;\r
+  }\r
+\r
+  public List<ObjectId> getMechanizedIds() {\r
+    return mechanizedIds;\r
+  }\r
+\r
+  public void setMechanizedIds(List<ObjectId> mechanizedIds) {\r
+    this.mechanizedIds = mechanizedIds;\r
+  }\r
+\r
+  public ObjectId getOwnerId() {\r
+    return ownerId;\r
+  }\r
+\r
+  public void setOwnerId(ObjectId ownerId) {\r
+    this.ownerId = ownerId;\r
+  }\r
+\r
+  public List<Role> getRoles() {\r
+    return roles;\r
+  }\r
+\r
+  public void setRoles(List<Role> roles) {\r
+    this.roles = roles;\r
+  }\r
+\r
+    public List<GroupMember> getMembers() {\r
+    return members;\r
+  }\r
+\r
+  public void setMembers(List<GroupMember> members) {\r
+    this.members = members;\r
+  }\r
+\r
+  public ObjectId getParentGroupId() {\r
+    return parentGroupId;\r
+  }\r
+\r
+  public void setParentGroupId(ObjectId parentGroupId) {\r
+    this.parentGroupId = parentGroupId;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/GroupMember.java b/otf-service-api/src/main/java/org/oran/otf/common/model/GroupMember.java
new file mode 100644 (file)
index 0000000..583c213
--- /dev/null
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.bson.types.ObjectId;\r
+\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+public class GroupMember {\r
+    private ObjectId userId;\r
+    private List<String> roles;//this is name of roles assigned to user that are created within the group i.e admin,dev,.. etc\r
+\r
+    public ObjectId getUserId() {\r
+        return userId;\r
+    }\r
+\r
+    public void setUserId(ObjectId userId) {\r
+        this.userId = userId;\r
+    }\r
+\r
+    public List<String> getRoles() {\r
+        return roles;\r
+    }\r
+\r
+    public void setRoles(List<String> roles) {\r
+        this.roles = roles;\r
+    }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/Role.java b/otf-service-api/src/main/java/org/oran/otf/common/model/Role.java
new file mode 100644 (file)
index 0000000..aca09f1
--- /dev/null
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import java.util.List;\r
+\r
+public class Role {\r
+\r
+    private String roleName;\r
+    private List<String> permissions;\r
+\r
+    public String getRoleName() {\r
+        return roleName;\r
+    }\r
+\r
+    public void setRoleName(String roleName) {\r
+        this.roleName = roleName;\r
+    }\r
+\r
+    public List<String> getPermissions() {\r
+        return permissions;\r
+    }\r
+\r
+    public void setPermissions(List<String> permissions) {\r
+        this.permissions = permissions;\r
+    }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/TestDefinition.java b/otf-service-api/src/main/java/org/oran/otf/common/model/TestDefinition.java
new file mode 100644 (file)
index 0000000..2a66fa2
--- /dev/null
@@ -0,0 +1,137 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testDefinitions")\r
+public class TestDefinition implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id private ObjectId _id;\r
+  private String testName;\r
+  private String testDescription;\r
+  private String processDefinitionKey;\r
+  private List<BpmnInstance> bpmnInstances;\r
+  private ObjectId groupId;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+  private boolean disabled;\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestName() {\r
+    return testName;\r
+  }\r
+\r
+  public void setTestName(String testName) {\r
+    this.testName = testName;\r
+  }\r
+\r
+  public String getTestDescription() {\r
+    return testDescription;\r
+  }\r
+\r
+  public void setTestDescription(String testDescription) {\r
+    this.testDescription = testDescription;\r
+  }\r
+\r
+  public String getProcessDefinitionKey() {\r
+    return processDefinitionKey;\r
+  }\r
+\r
+  public void setProcessDefinitionKey(String processDefinitionKey) {\r
+    this.processDefinitionKey = processDefinitionKey;\r
+  }\r
+\r
+  public List<BpmnInstance> getBpmnInstances() {\r
+    return bpmnInstances;\r
+  }\r
+\r
+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {\r
+    this.bpmnInstances = bpmnInstances;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public boolean isDisabled() {\r
+    return disabled;\r
+  }\r
+\r
+  public void setDisabled(boolean disabled) {\r
+    this.disabled = disabled;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/TestExecution.java b/otf-service-api/src/main/java/org/oran/otf/common/model/TestExecution.java
new file mode 100644 (file)
index 0000000..8f02e0b
--- /dev/null
@@ -0,0 +1,245 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.historic.TestDefinitionHistoric;\r
+import org.oran.otf.common.model.historic.TestInstanceHistoric;\r
+import org.oran.otf.common.model.local.TestHeadResult;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testExecutions")\r
+public class TestExecution implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+  private ObjectId groupId;\r
+  private ObjectId executorId;\r
+\r
+  private boolean async;\r
+  private Date startTime;\r
+  private Date endTime;\r
+  private String asyncTopic;\r
+  private String businessKey;\r
+  private String processInstanceId;\r
+  private String testResult;\r
+  private String testResultMessage;\r
+  private Map<String, Object> testDetails;\r
+  private List<TestHeadResult> testHeadResults;\r
+  private List<TestExecution> testInstanceResults;\r
+  // Stores historic information of associated\r
+  private String historicEmail;\r
+  private TestInstanceHistoric historicTestInstance;\r
+  private TestDefinitionHistoric historicTestDefinition;\r
+\r
+  public TestExecution() {\r
+  }\r
+\r
+  public TestExecution(\r
+          ObjectId _id,\r
+          ObjectId groupId,\r
+          ObjectId executorId,\r
+          boolean async,\r
+          Date startTime,\r
+          Date endTime,\r
+          String asyncTopic,\r
+          String businessKey,\r
+          String processInstanceId,\r
+          String testResult,\r
+          String testResultMessage,\r
+          Map<String, Object> testDetails,\r
+          List<TestHeadResult> testHeadResults,\r
+          List<TestExecution> testInstanceResults,\r
+          String historicEmail,\r
+          TestInstanceHistoric historicTestInstance,\r
+          TestDefinitionHistoric historicTestDefinition) {\r
+    this._id = _id;\r
+    this.groupId = groupId;\r
+    this.executorId = executorId;\r
+    this.async = async;\r
+    this.startTime = startTime;\r
+    this.endTime = endTime;\r
+    this.asyncTopic = asyncTopic;\r
+    this.businessKey = businessKey;\r
+    this.processInstanceId = processInstanceId;\r
+    this.testResult = testResult;\r
+    this.testDetails = testDetails;\r
+    this.testHeadResults = testHeadResults;\r
+    this.testInstanceResults = testInstanceResults;\r
+    this.historicEmail = historicEmail;\r
+    this.historicTestInstance = historicTestInstance;\r
+    this.historicTestDefinition = historicTestDefinition;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public ObjectId getExecutorId() {\r
+    return executorId;\r
+  }\r
+\r
+  public void setExecutorId(ObjectId executorId) {\r
+    this.executorId = executorId;\r
+  }\r
+\r
+  public boolean isAsync() {\r
+    return async;\r
+  }\r
+\r
+  public void setAsync(boolean async) {\r
+    this.async = async;\r
+  }\r
+\r
+  public Date getStartTime() {\r
+    return startTime;\r
+  }\r
+\r
+  public void setStartTime(Date startTime) {\r
+    this.startTime = startTime;\r
+  }\r
+\r
+  public Date getEndTime() {\r
+    return endTime;\r
+  }\r
+\r
+  public void setEndTime(Date endTime) {\r
+    this.endTime = endTime;\r
+  }\r
+\r
+  public String getAsyncTopic() {\r
+    return asyncTopic;\r
+  }\r
+\r
+  public void setAsyncTopic(String asyncTopic) {\r
+    this.asyncTopic = asyncTopic;\r
+  }\r
+\r
+  public String getBusinessKey() {\r
+    return businessKey;\r
+  }\r
+\r
+  public void setBusinessKey(String businessKey) {\r
+    this.businessKey = businessKey;\r
+  }\r
+\r
+  public String getProcessInstanceId() {\r
+    return processInstanceId;\r
+  }\r
+\r
+  public void setProcessInstanceId(String processInstanceId) {\r
+    this.processInstanceId = processInstanceId;\r
+  }\r
+\r
+  public String getTestResult() {\r
+    return testResult;\r
+  }\r
+\r
+  public void setTestResult(String testResult) {\r
+    this.testResult = testResult;\r
+  }\r
+\r
+  public String getTestResultMessage() {\r
+    return testResultMessage;\r
+  }\r
+\r
+  public void setTestResultMessage(String testResultMessage) {\r
+    this.testResultMessage = testResultMessage;\r
+  }\r
+\r
+  public Map<String, Object> getTestDetails() {\r
+    return testDetails;\r
+  }\r
+\r
+  public void setTestDetails(Map<String, Object> testDetails) {\r
+    this.testDetails = testDetails;\r
+  }\r
+\r
+  public List<TestHeadResult> getTestHeadResults() {\r
+    synchronized (testHeadResults) {\r
+      return testHeadResults;\r
+    }\r
+  }\r
+\r
+  public void setTestHeadResults(List<TestHeadResult> testHeadResults) {\r
+    synchronized (testHeadResults) {\r
+      this.testHeadResults = testHeadResults;\r
+    }\r
+  }\r
+\r
+  public List<TestExecution> getTestInstanceResults() {\r
+    synchronized (testInstanceResults) {\r
+      return testInstanceResults;\r
+    }\r
+  }\r
+\r
+  public void setTestInstanceResults(List<TestExecution> testInstanceResults) {\r
+    synchronized (testInstanceResults) {\r
+      this.testInstanceResults = testInstanceResults;\r
+    }\r
+  }\r
+\r
+  public String getHistoricEmail() {\r
+    return historicEmail;\r
+  }\r
+\r
+  public void setHistoricEmail(String historicEmail) {\r
+    this.historicEmail = historicEmail;\r
+  }\r
+\r
+  public TestInstanceHistoric getHistoricTestInstance() {\r
+    return historicTestInstance;\r
+  }\r
+\r
+  public void setHistoricTestInstance(TestInstanceHistoric historicTestInstance) {\r
+    this.historicTestInstance = historicTestInstance;\r
+  }\r
+\r
+  public TestDefinitionHistoric getHistoricTestDefinition() {\r
+    return historicTestDefinition;\r
+  }\r
+\r
+  public void setHistoricTestDefinition(\r
+          TestDefinitionHistoric historicTestDefinition) {\r
+    this.historicTestDefinition = historicTestDefinition;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/TestHead.java b/otf-service-api/src/main/java/org/oran/otf/common/model/TestHead.java
new file mode 100644 (file)
index 0000000..7f4bcbc
--- /dev/null
@@ -0,0 +1,224 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.Map;\r
+\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.index.Indexed;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testHeads")\r
+public class TestHead implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  @Id\r
+  private ObjectId _id;\r
+\r
+  @Indexed(unique = true)\r
+  private String testHeadName;\r
+\r
+  private String testHeadDescription;\r
+  private String hostname;\r
+  private String port;\r
+  private String resourcePath;\r
+  private ObjectId creatorId;\r
+  private ObjectId groupId;\r
+  private String authorizationType;\r
+  private String authorizationCredential;\r
+  private Boolean authorizationEnabled;\r
+  private Map<String, Object> vthInputTemplate;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId updatedBy;\r
+  private Boolean isPublic;\r
+  public TestHead() {\r
+  }\r
+\r
+  public TestHead(\r
+          ObjectId _id,\r
+          String testHeadName,\r
+          String testHeadDescription,\r
+          String hostname,\r
+          String port,\r
+          String resourcePath,\r
+          ObjectId creatorId,\r
+          ObjectId groupId,\r
+          String authorizationType,\r
+          String authorizationCredential,\r
+          boolean authorizationEnabled,\r
+          Map<String, Object> vthInputTemplate,\r
+          Date createdAt,\r
+          Date updatedAt,\r
+          ObjectId updatedBy,\r
+          Boolean isPublic) {\r
+    this._id = _id;\r
+    this.testHeadName = testHeadName;\r
+    this.testHeadDescription = testHeadDescription;\r
+    this.hostname = hostname;\r
+    this.port = port;\r
+    this.resourcePath = resourcePath;\r
+    this.creatorId = creatorId;\r
+    this.groupId = groupId;\r
+    this.authorizationType = authorizationType;\r
+    this.authorizationCredential = authorizationCredential;\r
+    this.authorizationEnabled = authorizationEnabled;\r
+    this.vthInputTemplate = vthInputTemplate;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.updatedBy = updatedBy;\r
+    this.isPublic = isPublic;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestHeadName() {\r
+    return testHeadName;\r
+  }\r
+\r
+  public void setTestHeadName(String testHeadName) {\r
+    this.testHeadName = testHeadName;\r
+  }\r
+\r
+  public String getTestHeadDescription() {\r
+    return testHeadDescription;\r
+  }\r
+\r
+  public void setTestHeadDescription(String testHeadDescription) {\r
+    this.testHeadDescription = testHeadDescription;\r
+  }\r
+\r
+  public String getHostname() {\r
+    return hostname;\r
+  }\r
+\r
+  public void setHostname(String hostname) {\r
+    this.hostname = hostname;\r
+  }\r
+\r
+  public String getPort() {\r
+    return port;\r
+  }\r
+\r
+  public void setPort(String port) {\r
+    this.port = port;\r
+  }\r
+\r
+  public String getResourcePath() {\r
+    return resourcePath;\r
+  }\r
+\r
+  public void setResourcePath(String resourcePath) {\r
+    this.resourcePath = resourcePath;\r
+  }\r
+\r
+  public ObjectId getCreatorId() {\r
+    return creatorId;\r
+  }\r
+\r
+  public void setCreatorId(ObjectId creatorId) {\r
+    this.creatorId = creatorId;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public String getAuthorizationCredential() {\r
+    return authorizationCredential;\r
+  }\r
+\r
+  public String getAuthorizationType() {\r
+    return authorizationType;\r
+  }\r
+\r
+  public void setAuthorizationType(String authorizationType) {\r
+    this.authorizationType = authorizationType;\r
+  }\r
+\r
+  public void setAuthorizationCredential(String authorizationCredential) {\r
+    this.authorizationCredential = authorizationCredential;\r
+  }\r
+\r
+  public Boolean getAuthorizationEnabled() {\r
+    return authorizationEnabled;\r
+  }\r
+\r
+  public void setAuthorizationEnabled(Boolean authorizationEnabled) {\r
+    this.authorizationEnabled = authorizationEnabled;\r
+  }\r
+\r
+  public Map<String, Object> getVthInputTemplate() {\r
+    return vthInputTemplate;\r
+  }\r
+\r
+  public void setVthInputTemplate(Map<String, Object> vthInputTemplate) {\r
+    this.vthInputTemplate = vthInputTemplate;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public Boolean isPublic() {\r
+    return isPublic;\r
+  }\r
+\r
+  public void setPublic(Boolean aPublic) {\r
+    isPublic = aPublic;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/TestInstance.java b/otf-service-api/src/main/java/org/oran/otf/common/model/TestInstance.java
new file mode 100644 (file)
index 0000000..9b11fa4
--- /dev/null
@@ -0,0 +1,256 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "testInstances")\r
+public class TestInstance implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private @Id ObjectId _id;\r
+  private String testInstanceName;\r
+  private String testInstanceDescription;\r
+  private ObjectId groupId;\r
+  private ObjectId testDefinitionId;\r
+  private String processDefinitionId;\r
+  private boolean useLatestTestDefinition;\r
+  private boolean disabled;\r
+  private boolean simulationMode;\r
+  private long maxExecutionTimeInMillis;\r
+  private HashMap<String, ParallelFlowInput> pfloInput;\r
+  private HashMap<String, Object> internalTestData;\r
+  private HashMap<String, Object> simulationVthInput;\r
+  private HashMap<String, Object> testData;\r
+  private HashMap<String, Object> vthInput;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+\r
+  public TestInstance() {}\r
+\r
+  public TestInstance(\r
+      ObjectId _id,\r
+      String testInstanceName,\r
+      String testInstanceDescription,\r
+      ObjectId groupId,\r
+      ObjectId testDefinitionId,\r
+      String processDefinitionId,\r
+      boolean useLatestTestDefinition,\r
+      boolean disabled,\r
+      boolean simulationMode,\r
+      long maxExecutionTimeInMillis,\r
+      HashMap<String, ParallelFlowInput> pfloInput,\r
+      HashMap<String, Object> internalTestData,\r
+      HashMap<String, Object> simulationVthInput,\r
+      HashMap<String, Object> testData,\r
+      HashMap<String, Object> vthInput,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy) {\r
+    this._id = _id;\r
+    this.testInstanceName = testInstanceName;\r
+    this.testInstanceDescription = testInstanceDescription;\r
+    this.groupId = groupId;\r
+    this.testDefinitionId = testDefinitionId;\r
+    this.processDefinitionId = processDefinitionId;\r
+    this.useLatestTestDefinition = useLatestTestDefinition;\r
+    this.disabled = disabled;\r
+    this.simulationMode = simulationMode;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+    this.pfloInput = pfloInput;\r
+    this.internalTestData = internalTestData;\r
+    this.simulationVthInput = simulationVthInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestInstanceName() {\r
+    return testInstanceName;\r
+  }\r
+\r
+  public void setTestInstanceName(String testInstanceName) {\r
+    this.testInstanceName = testInstanceName;\r
+  }\r
+\r
+  public String getTestInstanceDescription() {\r
+    return testInstanceDescription;\r
+  }\r
+\r
+  public void setTestInstanceDescription(String testInstanceDescription) {\r
+    this.testInstanceDescription = testInstanceDescription;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionId() {\r
+    return testDefinitionId;\r
+  }\r
+\r
+  public void setTestDefinitionId(ObjectId testDefinitionId) {\r
+    this.testDefinitionId = testDefinitionId;\r
+  }\r
+\r
+  public String getProcessDefinitionId() {\r
+    return processDefinitionId;\r
+  }\r
+\r
+  public void setProcessDefinitionId(String processDefinitionId) {\r
+    this.processDefinitionId = processDefinitionId;\r
+  }\r
+\r
+  public boolean isUseLatestTestDefinition() {\r
+    return useLatestTestDefinition;\r
+  }\r
+\r
+  public void setUseLatestTestDefinition(boolean useLatestTestDefinition) {\r
+    this.useLatestTestDefinition = useLatestTestDefinition;\r
+  }\r
+\r
+  public boolean isDisabled() {\r
+    return disabled;\r
+  }\r
+\r
+  public void setDisabled(boolean disabled) {\r
+    this.disabled = disabled;\r
+  }\r
+\r
+  public boolean isSimulationMode() {\r
+    return simulationMode;\r
+  }\r
+\r
+  public void setSimulationMode(boolean simulationMode) {\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  public long getMaxExecutionTimeInMillis() {\r
+    return maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public HashMap<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(HashMap<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public HashMap<String, Object> getInternalTestData() {\r
+    return internalTestData;\r
+  }\r
+\r
+  public void setInternalTestData(HashMap<String, Object> internalTestData) {\r
+    this.internalTestData = internalTestData;\r
+  }\r
+\r
+  public HashMap<String, Object> getSimulationVthInput() {\r
+    return simulationVthInput;\r
+  }\r
+\r
+  public void setSimulationVthInput(HashMap<String, Object> simulationVthInput) {\r
+    this.simulationVthInput = simulationVthInput;\r
+  }\r
+\r
+  public HashMap<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(HashMap<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public HashMap<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(HashMap<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/User.java b/otf-service-api/src/main/java/org/oran/otf/common/model/User.java
new file mode 100644 (file)
index 0000000..2c56b85
--- /dev/null
@@ -0,0 +1,142 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model;\r
+\r
+import org.oran.otf.common.model.local.UserGroup;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.core.mapping.Document;\r
+\r
+@Document(collection = "users")\r
+public class User implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId _id;\r
+  private List<String> permissions;\r
+  private String firstName;\r
+  private String lastName;\r
+  private String email;\r
+  private String password;\r
+  private List<UserGroup> groups;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+\r
+  //Added User for testing\r
+  public User(){};\r
+\r
+  public User(\r
+      ObjectId _id,\r
+      List<String> permissions,\r
+      String firstName,\r
+      String lastName,\r
+      String email,\r
+      String password,\r
+      List<UserGroup> groups,\r
+      Date createdAt,\r
+      Date updatedAt) {\r
+    this._id = _id;\r
+    this.permissions = permissions;\r
+    this.firstName = firstName;\r
+    this.lastName = lastName;\r
+    this.email = email;\r
+    this.password = password;\r
+    this.groups = groups;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public List<String> getPermissions() {\r
+    return permissions;\r
+  }\r
+\r
+  public void setPermissions(List<String> permissions) {\r
+    this.permissions = permissions;\r
+  }\r
+\r
+  public String getFirstName() {\r
+    return firstName;\r
+  }\r
+\r
+  public void setFirstName(String firstName) {\r
+    this.firstName = firstName;\r
+  }\r
+\r
+  public String getLastName() {\r
+    return lastName;\r
+  }\r
+\r
+  public void setLastName(String lastName) {\r
+    this.lastName = lastName;\r
+  }\r
+\r
+  public String getEmail() {\r
+    return email;\r
+  }\r
+\r
+  public void setEmail(String email) {\r
+    this.email = email;\r
+  }\r
+\r
+  public String getPassword() {\r
+    return password;\r
+  }\r
+\r
+  public void setPassword(String password) {\r
+    this.password = password;\r
+  }\r
+\r
+  public List<UserGroup> getGroups() {\r
+    return groups;\r
+  }\r
+\r
+  public void setGroups(List<UserGroup> groups) {\r
+    this.groups = groups;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java b/otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java
new file mode 100644 (file)
index 0000000..fe2be4b
--- /dev/null
@@ -0,0 +1,185 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.historic;\r
+\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.Date;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+\r
+public class TestDefinitionHistoric implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId _id;\r
+  private String testName;\r
+  private String testDescription;\r
+  private String processDefinitionKey;\r
+  private List<BpmnInstance> bpmnInstances;\r
+  private ObjectId groupId;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+\r
+  public TestDefinitionHistoric() {\r
+  }\r
+\r
+  public TestDefinitionHistoric(TestDefinition testDefinition, String processDefinitionId) {\r
+    this._id = testDefinition.get_id();\r
+    this.testName = testDefinition.getTestName();\r
+    this.testDescription = testDefinition.getTestDescription();\r
+    this.processDefinitionKey = testDefinition.getProcessDefinitionKey();\r
+    this.bpmnInstances =\r
+        getHistoricBpmnInstanceAsList(testDefinition.getBpmnInstances(), processDefinitionId);\r
+    this.groupId = testDefinition.getGroupId();\r
+    this.createdAt = testDefinition.getCreatedAt();\r
+    this.updatedAt = testDefinition.getUpdatedAt();\r
+    this.createdBy = testDefinition.getCreatedBy();\r
+    this.updatedBy = testDefinition.getUpdatedBy();\r
+  }\r
+\r
+  public TestDefinitionHistoric(\r
+      ObjectId _id,\r
+      String testName,\r
+      String testDescription,\r
+      String processDefinitionKey,\r
+      List<BpmnInstance> bpmnInstances,\r
+      ObjectId groupId,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy) {\r
+    this._id = _id;\r
+    this.testName = testName;\r
+    this.testDescription = testDescription;\r
+    this.processDefinitionKey = processDefinitionKey;\r
+    this.bpmnInstances = bpmnInstances;\r
+    this.groupId = groupId;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  private List<BpmnInstance> getHistoricBpmnInstanceAsList(\r
+      List<BpmnInstance> bpmnInstances, String processDefinitionId) {\r
+    BpmnInstance bpmnInstance =\r
+        bpmnInstances.stream()\r
+            .filter(\r
+                _bpmnInstance ->\r
+                    _bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(processDefinitionId))\r
+            .findFirst()\r
+            .orElse(null);\r
+\r
+    List<BpmnInstance> historicBpmnInstance = new ArrayList<>();\r
+    if (bpmnInstance != null) {\r
+      historicBpmnInstance.add(bpmnInstance);\r
+    }\r
+\r
+    return historicBpmnInstance;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestName() {\r
+    return testName;\r
+  }\r
+\r
+  public void setTestName(String testName) {\r
+    this.testName = testName;\r
+  }\r
+\r
+  public String getTestDescription() {\r
+    return testDescription;\r
+  }\r
+\r
+  public void setTestDescription(String testDescription) {\r
+    this.testDescription = testDescription;\r
+  }\r
+\r
+  public String getProcessDefinitionKey() {\r
+    return processDefinitionKey;\r
+  }\r
+\r
+  public void setProcessDefinitionKey(String processDefinitionKey) {\r
+    this.processDefinitionKey = processDefinitionKey;\r
+  }\r
+\r
+  public List<BpmnInstance> getBpmnInstances() {\r
+    return bpmnInstances;\r
+  }\r
+\r
+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {\r
+    this.bpmnInstances = bpmnInstances;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java b/otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java
new file mode 100644 (file)
index 0000000..1263893
--- /dev/null
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.historic;\r
+\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.annotation.Id;\r
+\r
+public class TestInstanceHistoric implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private @Id\r
+  ObjectId _id;\r
+  private String testInstanceName;\r
+  private String testInstanceDescription;\r
+  private ObjectId groupId;\r
+  private ObjectId testDefinitionId;\r
+  private String processDefinitionId;\r
+  private Map<String, ParallelFlowInput> pfloInput;\r
+  private Map<String, Object> simulationVthInput;\r
+  private Map<String, Object> testData;\r
+  private Map<String, Object> vthInput;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+  private boolean simulationMode;\r
+\r
+  public TestInstanceHistoric() {\r
+  }\r
+\r
+  public TestInstanceHistoric(TestInstance testInstance) {\r
+    this._id = testInstance.get_id();\r
+    this.testInstanceName = testInstance.getTestInstanceName();\r
+    this.testInstanceDescription = testInstance.getTestInstanceDescription();\r
+    this.groupId = testInstance.getGroupId();\r
+    this.testDefinitionId = testInstance.getTestDefinitionId();\r
+    this.pfloInput = testInstance.getPfloInput();\r
+    this.processDefinitionId = testInstance.getProcessDefinitionId();\r
+    this.simulationVthInput = testInstance.getSimulationVthInput();\r
+    this.testData = testInstance.getTestData();\r
+    this.vthInput = testInstance.getVthInput();\r
+    this.createdAt = testInstance.getCreatedAt();\r
+    this.updatedAt = testInstance.getUpdatedAt();\r
+    this.createdBy = testInstance.getCreatedBy();\r
+    this.updatedBy = testInstance.getUpdatedBy();\r
+    this.simulationMode = testInstance.isSimulationMode();\r
+  }\r
+\r
+  public TestInstanceHistoric(\r
+      ObjectId _id,\r
+      String testInstanceName,\r
+      String testInstanceDescription,\r
+      ObjectId groupId,\r
+      ObjectId testDefinitionId,\r
+      String processDefinitionId,\r
+      HashMap<String, ParallelFlowInput> pfloInput,\r
+      HashMap<String, Object> simulationVthInput,\r
+      HashMap<String, Object> testData,\r
+      HashMap<String, Object> vthInput,\r
+      Date createdAt,\r
+      Date updatedAt,\r
+      ObjectId createdBy,\r
+      ObjectId updatedBy,\r
+      boolean simulationMode) {\r
+    this._id = _id;\r
+    this.testInstanceName = testInstanceName;\r
+    this.testInstanceDescription = testInstanceDescription;\r
+    this.groupId = groupId;\r
+    this.testDefinitionId = testDefinitionId;\r
+    this.processDefinitionId = processDefinitionId;\r
+    this.pfloInput = pfloInput;\r
+    this.simulationVthInput = simulationVthInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public ObjectId get_id() {\r
+    return _id;\r
+  }\r
+\r
+  public void set_id(ObjectId _id) {\r
+    this._id = _id;\r
+  }\r
+\r
+  public String getTestInstanceName() {\r
+    return testInstanceName;\r
+  }\r
+\r
+  public void setTestInstanceName(String testInstanceName) {\r
+    this.testInstanceName = testInstanceName;\r
+  }\r
+\r
+  public String getTestInstanceDescription() {\r
+    return testInstanceDescription;\r
+  }\r
+\r
+  public void setTestInstanceDescription(String testInstanceDescription) {\r
+    this.testInstanceDescription = testInstanceDescription;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionId() {\r
+    return testDefinitionId;\r
+  }\r
+\r
+  public void setTestDefinitionId(ObjectId testDefinitionId) {\r
+    this.testDefinitionId = testDefinitionId;\r
+  }\r
+\r
+  public String getProcessDefinitionId() {\r
+    return processDefinitionId;\r
+  }\r
+\r
+  public void setProcessDefinitionId(String processDefinitionId) {\r
+    this.processDefinitionId = processDefinitionId;\r
+  }\r
+\r
+  public Map<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(\r
+      HashMap<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public Map<String, Object> getSimulationVthInput() {\r
+    return simulationVthInput;\r
+  }\r
+\r
+  public void setSimulationVthInput(\r
+      HashMap<String, Object> simulationVthInput) {\r
+    this.simulationVthInput = simulationVthInput;\r
+  }\r
+\r
+  public Map<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(HashMap<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public Map<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(HashMap<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public boolean isSimulationMode() {\r
+    return simulationMode;\r
+  }\r
+\r
+  public void setSimulationMode(boolean simulationMode) {\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/ApiRequest.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/ApiRequest.java
new file mode 100644 (file)
index 0000000..05ec6a9
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+public class ApiRequest {}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java
new file mode 100644 (file)
index 0000000..c4440b0
--- /dev/null
@@ -0,0 +1,191 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import com.fasterxml.jackson.annotation.JsonCreator;\r
+import com.fasterxml.jackson.annotation.JsonProperty;\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.List;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+public class BpmnInstance implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private String processDefinitionId;\r
+  private String deploymentId;\r
+  private int version;\r
+  private ObjectId bpmnFileId;\r
+  private ObjectId resourceFileId;\r
+  private boolean isDeployed;\r
+  private List<TestHeadNode> testHeads;\r
+  private List<PfloNode> pflos;\r
+  private Map<String, Object> testDataTemplate;\r
+  private Date createdAt;\r
+  private Date updatedAt;\r
+  private ObjectId createdBy;\r
+  private ObjectId updatedBy;\r
+\r
+  public BpmnInstance() {\r
+  }\r
+\r
+  @JsonCreator\r
+  public BpmnInstance(\r
+      @JsonProperty("processDefinitionId") String processDefinitionId,\r
+      @JsonProperty("deploymentId") String deploymentId,\r
+      @JsonProperty("version") int version,\r
+      @JsonProperty("bpmnFileId") ObjectId bpmnFileId,\r
+      @JsonProperty("resourceFileId") ObjectId resourceFileId,\r
+      @JsonProperty("isDeployed") boolean isDeployed,\r
+      @JsonProperty("testHeads") List<TestHeadNode> testHeads,\r
+      @JsonProperty("plfos") List<PfloNode> pflos,\r
+      @JsonProperty("testDataTemplate") Map<String, Object> testDataTemplate,\r
+      @JsonProperty("createdAt") Date createdAt,\r
+      @JsonProperty("updateAt") Date updatedAt,\r
+      @JsonProperty("createdBy") ObjectId createdBy,\r
+      @JsonProperty("updatedBy") ObjectId updatedBy) {\r
+    this.processDefinitionId = processDefinitionId;\r
+    this.deploymentId = deploymentId;\r
+    this.version = version;\r
+    this.bpmnFileId = bpmnFileId;\r
+    this.resourceFileId = resourceFileId;\r
+    this.isDeployed = isDeployed;\r
+    this.testHeads = testHeads;\r
+    this.testDataTemplate = testDataTemplate;\r
+    this.createdAt = createdAt;\r
+    this.updatedAt = updatedAt;\r
+    this.createdBy = createdBy;\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  public String getProcessDefinitionId() {\r
+    return processDefinitionId;\r
+  }\r
+\r
+  public void setProcessDefinitionId(String processDefinitionId) {\r
+    this.processDefinitionId = processDefinitionId;\r
+  }\r
+\r
+  public String getDeploymentId() {\r
+    return deploymentId;\r
+  }\r
+\r
+  public void setDeploymentId(String deploymentId) {\r
+    this.deploymentId = deploymentId;\r
+  }\r
+\r
+  public int getVersion() {\r
+    return version;\r
+  }\r
+\r
+  public void setVersion(int version) {\r
+    this.version = version;\r
+  }\r
+\r
+  public ObjectId getBpmnFileId() {\r
+    return bpmnFileId;\r
+  }\r
+\r
+  public void setBpmnFileId(ObjectId bpmnFileId) {\r
+    this.bpmnFileId = bpmnFileId;\r
+  }\r
+\r
+  public ObjectId getResourceFileId() {\r
+    return resourceFileId;\r
+  }\r
+\r
+  public void setResourceFileId(ObjectId resourceFileId) {\r
+    this.resourceFileId = resourceFileId;\r
+  }\r
+\r
+  @JsonProperty(value="isDeployed")\r
+  public boolean isDeployed() {\r
+    return isDeployed;\r
+  }\r
+\r
+  public void setDeployed(boolean deployed) {\r
+    isDeployed = deployed;\r
+  }\r
+\r
+  public List<TestHeadNode> getTestHeads() {\r
+    return testHeads;\r
+  }\r
+\r
+  public void setTestHeads(List<TestHeadNode> testHeads) {\r
+    this.testHeads = testHeads;\r
+  }\r
+\r
+  public List<PfloNode> getPflos() {\r
+    return pflos;\r
+  }\r
+\r
+  public void setPflos(List<PfloNode> pflos) {\r
+    this.pflos = pflos;\r
+  }\r
+\r
+  public Map<String, Object> getTestDataTemplate() {\r
+    return testDataTemplate;\r
+  }\r
+\r
+  public void setTestDataTemplate(Map<String, Object> testDataTemplate) {\r
+    this.testDataTemplate = testDataTemplate;\r
+  }\r
+\r
+  public Date getCreatedAt() {\r
+    return createdAt;\r
+  }\r
+\r
+  public void setCreatedAt(Date createdAt) {\r
+    this.createdAt = createdAt;\r
+  }\r
+\r
+  public Date getUpdatedAt() {\r
+    return updatedAt;\r
+  }\r
+\r
+  public void setUpdatedAt(Date updatedAt) {\r
+    this.updatedAt = updatedAt;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public ObjectId getUpdatedBy() {\r
+    return updatedBy;\r
+  }\r
+\r
+  public void setUpdatedBy(ObjectId updatedBy) {\r
+    this.updatedBy = updatedBy;\r
+  }\r
+\r
+  private String getObjectIdString(ObjectId value) {\r
+    return value == null ? "\"\"" : "\"" + value.toString() + "\"";\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/DeployTestStrategyRequest.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/DeployTestStrategyRequest.java
new file mode 100644 (file)
index 0000000..16040e7
--- /dev/null
@@ -0,0 +1,73 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.bson.types.ObjectId;\r
+\r
+public class DeployTestStrategyRequest {\r
+  private ObjectId testDefinitionDeployerId;\r
+  private ObjectId testDefinitionId;\r
+  private String definitionId;\r
+\r
+  public DeployTestStrategyRequest() {}\r
+\r
+  public DeployTestStrategyRequest(\r
+      ObjectId testDefinitionDeployerId, ObjectId testDefinitionId, String definitionId) {\r
+    this.testDefinitionDeployerId = testDefinitionDeployerId;\r
+    this.testDefinitionId = testDefinitionId;\r
+    this.definitionId = definitionId;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionDeployerId() {\r
+    return testDefinitionDeployerId;\r
+  }\r
+\r
+  public void setTestDefinitionDeployerId(ObjectId testDefinitionDeployerId) {\r
+    this.testDefinitionDeployerId = testDefinitionDeployerId;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionId() {\r
+    return testDefinitionId;\r
+  }\r
+\r
+  public void setTestDefinitionId(ObjectId testDefinitionId) {\r
+    this.testDefinitionId = testDefinitionId;\r
+  }\r
+\r
+  public String getDefinitionId() {\r
+    return definitionId;\r
+  }\r
+\r
+  public void setDefinitionId(String definitionId) {\r
+    this.definitionId = definitionId;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return "{\"DeployTestStrategyRequest\":{"\r
+        + "\"testDefinitionDeployerId\":\""\r
+        + testDefinitionDeployerId\r
+        + "\""\r
+        + ", \"testDefinitionId\":\""\r
+        + testDefinitionId\r
+        + "\""\r
+        + ", \"definitionId\":\""\r
+        + definitionId\r
+        + "\""\r
+        + "}}";\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java
new file mode 100644 (file)
index 0000000..e4f959e
--- /dev/null
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+\r
+import java.util.Date;\r
+\r
+public class OTFApiResponse {\r
+\r
+    private int statusCode;\r
+    private String message;\r
+    private Date time;\r
+\r
+    public OTFApiResponse() {\r
+    }\r
+\r
+    public OTFApiResponse(int statusCode, String message) {\r
+        this.statusCode = statusCode;\r
+        this.message = message;\r
+        this.time = new Date(System.currentTimeMillis());\r
+    }\r
+\r
+    public int getStatusCode() {\r
+        return statusCode;\r
+    }\r
+\r
+    public void setStatusCode(int statusCode) {\r
+        this.statusCode = statusCode;\r
+    }\r
+\r
+    public String getMessage() {\r
+        return message;\r
+    }\r
+\r
+    public void setMessage(String message) {\r
+        this.message = message;\r
+    }\r
+\r
+    public Date getTime() {\r
+        return time;\r
+    }\r
+\r
+    public void setTime(Date time) {\r
+        this.time = time;\r
+    }\r
+\r
+    @Override\r
+    public String toString() {\r
+        return Convert.objectToJson(this);\r
+    }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java
new file mode 100644 (file)
index 0000000..2ac94e1
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+\r
+import java.io.Serializable;\r
+import java.util.List;\r
+\r
+public class ParallelFlowInput implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private List<WorkflowRequest> args;\r
+  private boolean interruptOnFailure;\r
+  private int maxFailures;\r
+  private int threadPoolSize;\r
+\r
+  public ParallelFlowInput() {}\r
+\r
+  public ParallelFlowInput(\r
+      List<WorkflowRequest> args, boolean interruptOnFailure, int maxFailures, int threadPoolSize) {\r
+    this.args = args;\r
+    this.interruptOnFailure = interruptOnFailure;\r
+    this.maxFailures = maxFailures;\r
+    this.threadPoolSize = threadPoolSize;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public List<WorkflowRequest> getArgs() {\r
+    return args;\r
+  }\r
+\r
+  public void setArgs(List<WorkflowRequest> args) {\r
+    this.args = args;\r
+  }\r
+\r
+  public boolean isInterruptOnFailure() {\r
+    return interruptOnFailure;\r
+  }\r
+\r
+  public void setInterruptOnFailure(boolean interruptOnFailure) {\r
+    this.interruptOnFailure = interruptOnFailure;\r
+  }\r
+\r
+  public int getMaxFailures() {\r
+    return maxFailures;\r
+  }\r
+\r
+  public void setMaxFailures(int maxFailures) {\r
+    this.maxFailures = maxFailures;\r
+  }\r
+\r
+  public int getThreadPoolSize() {\r
+    return threadPoolSize;\r
+  }\r
+\r
+  public void setThreadPoolSize(int threadPoolSize) {\r
+    this.threadPoolSize = threadPoolSize;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/PfloNode.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/PfloNode.java
new file mode 100644 (file)
index 0000000..d8a8bd5
--- /dev/null
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+\r
+import java.io.Serializable;\r
+\r
+public class PfloNode implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private String bpmnPlfoTaskId;\r
+  private String label;\r
+\r
+  public PfloNode() {}\r
+\r
+  public PfloNode(String bpmnPlfoTaskId, String label) {\r
+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;\r
+    this.label = label;\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public String getBpmnPlfoTaskId() {\r
+    return bpmnPlfoTaskId;\r
+  }\r
+\r
+  public void setBpmnPlfoTaskId(String bpmnPlfoTaskId) {\r
+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;\r
+  }\r
+\r
+  public String getLabel() {\r
+    return label;\r
+  }\r
+\r
+  public void setLabel(String label) {\r
+    this.label = label;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java
new file mode 100644 (file)
index 0000000..99ed995
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import org.bson.types.ObjectId;\r
+\r
+public class TestHeadNode implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId testHeadId;\r
+  private String bpmnVthTaskId;\r
+\r
+  public TestHeadNode() {\r
+  }\r
+\r
+  public TestHeadNode(ObjectId testHeadId, String taskId) {\r
+    this.testHeadId = testHeadId;\r
+    this.bpmnVthTaskId = taskId;\r
+  }\r
+\r
+  public ObjectId getTestHeadId() {\r
+    return testHeadId;\r
+  }\r
+\r
+  public void setTestHeadId(ObjectId testHeadId) {\r
+    this.testHeadId = testHeadId;\r
+  }\r
+\r
+  public String getBpmnVthTaskId() {\r
+    return bpmnVthTaskId;\r
+  }\r
+\r
+  public void setBpmnVthTaskId(String bpmnVthTaskId) {\r
+    this.bpmnVthTaskId = bpmnVthTaskId;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java
new file mode 100644 (file)
index 0000000..89c7457
--- /dev/null
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import java.io.Serializable;\r
+import java.util.Map;\r
+\r
+public class TestHeadRequest implements Serializable {\r
+  private static final long serialVersionUID = 1L;\r
+  private Map<String, String> headers;\r
+  private Map<String, Object>  body;\r
+\r
+  public TestHeadRequest(){}\r
+\r
+  public TestHeadRequest(Map<String, String> headers,\r
+                         Map<String, Object> body) {\r
+    this.headers = headers;\r
+    this.body = body;\r
+  }\r
+\r
+  public Map<String, String> getHeaders() {\r
+    return headers;\r
+  }\r
+\r
+  public void setHeaders(Map<String, String> headers) {\r
+    this.headers = headers;\r
+  }\r
+\r
+  public Map<String, Object> getBody() {\r
+    return body;\r
+  }\r
+\r
+  public void setBody(Map<String, Object> body) {\r
+    this.body = body;\r
+  }\r
+\r
+\r
+\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java
new file mode 100644 (file)
index 0000000..55f82e9
--- /dev/null
@@ -0,0 +1,146 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import org.bson.types.ObjectId;\r
+\r
+import java.io.Serializable;\r
+import java.util.Date;\r
+import java.util.Map;\r
+\r
+public class TestHeadResult implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId testHeadId;\r
+  private String testHeadName;\r
+  private ObjectId testHeadGroupId;\r
+  private String bpmnVthTaskId;\r
+\r
+  //TODO: RG Remove maps below, setters and getters to return to normal\r
+  //private Map<String, String> testHeadHeaders;\r
+  //private int testHeadCode;\r
+  private int statusCode;\r
+\r
+  private TestHeadRequest testHeadRequest;\r
+  private Map<String, Object> testHeadResponse;\r
+  private Date startTime;\r
+  private Date endTime;\r
+\r
+  public TestHeadResult() {\r
+  }\r
+\r
+  public TestHeadResult(\r
+      ObjectId testHeadId,\r
+      String testHeadName,\r
+      ObjectId testHeadGroupId,\r
+      String bpmnVthTaskId,\r
+\r
+      //TODO: RG changed code to int and changed testHeadRequest from Map<String, String> to RequestContent\r
+      int statusCode,\r
+\r
+      TestHeadRequest testHeadRequest,\r
+      Map<String, Object> testHeadResponse,\r
+      Date startTime,\r
+      Date endTime) {\r
+    this.testHeadId = testHeadId;\r
+    this.testHeadName = testHeadName;\r
+    this.testHeadGroupId = testHeadGroupId;\r
+    this.bpmnVthTaskId = bpmnVthTaskId;\r
+\r
+    //this.testHeadHeaders = testHeadHeaders;\r
+    this.statusCode = statusCode;\r
+\r
+    this.testHeadRequest = testHeadRequest;\r
+    this.testHeadResponse = testHeadResponse;\r
+    this.startTime = startTime;\r
+    this.endTime = endTime;\r
+  }\r
+\r
+  public int getStatusCode(){return statusCode;}\r
+  public void setStatusCode(int testHeadCode){this.statusCode = statusCode;}\r
+\r
+  public ObjectId getTestHeadId() {\r
+    return testHeadId;\r
+  }\r
+\r
+  public void setTestHeadId(ObjectId testHeadId) {\r
+    this.testHeadId = testHeadId;\r
+  }\r
+\r
+  public String getTestHeadName() {\r
+    return testHeadName;\r
+  }\r
+\r
+  public void setTestHeadName(String testHeadName) {\r
+    this.testHeadName = testHeadName;\r
+  }\r
+\r
+  public ObjectId getTestHeadGroupId() {\r
+    return testHeadGroupId;\r
+  }\r
+\r
+  public void setTestHeadGroupId(ObjectId testHeadGroupId) {\r
+    this.testHeadGroupId = testHeadGroupId;\r
+  }\r
+\r
+  public String getBpmnVthTaskId() {\r
+    return bpmnVthTaskId;\r
+  }\r
+\r
+  public void setBpmnVthTaskId(String bpmnVthTaskId) {\r
+    this.bpmnVthTaskId = bpmnVthTaskId;\r
+  }\r
+\r
+  public TestHeadRequest getTestHeadRequest() {\r
+    return testHeadRequest;\r
+  }\r
+\r
+  public void setTestHeadRequest(TestHeadRequest testHeadRequest) {\r
+    this.testHeadRequest = testHeadRequest;\r
+  }\r
+\r
+  public Map<String, Object> getTestHeadResponse() {\r
+    return testHeadResponse;\r
+  }\r
+\r
+  public void setTestHeadResponse(Map<String, Object> testHeadResponse) {\r
+    this.testHeadResponse = testHeadResponse;\r
+  }\r
+\r
+  public Date getStartTime() {\r
+    return startTime;\r
+  }\r
+\r
+  public void setStartTime(Date startTime) {\r
+    this.startTime = startTime;\r
+  }\r
+\r
+  public Date getEndTime() {\r
+    return endTime;\r
+  }\r
+\r
+  public void setEndTime(Date endTime) {\r
+    this.endTime = endTime;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestInstanceCreateRequest.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestInstanceCreateRequest.java
new file mode 100644 (file)
index 0000000..b497477
--- /dev/null
@@ -0,0 +1,215 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import com.google.common.base.Strings;\r
+import java.io.Serializable;\r
+import java.util.HashMap;\r
+import org.bson.types.ObjectId;\r
+\r
+public class TestInstanceCreateRequest implements Serializable {\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId testDefinitionId = null;\r
+  private int version = Integer.MIN_VALUE;\r
+  private String processDefinitionKey = null;\r
+\r
+  private String testInstanceName;\r
+  private String testInstanceDescription;\r
+  private HashMap<String, ParallelFlowInput> pfloInput;\r
+  private HashMap<String, Object> simulationVthInput;\r
+  private HashMap<String, Object> testData;\r
+  private HashMap<String, Object> vthInput;\r
+  private ObjectId createdBy;\r
+  private boolean useLatestTestDefinition = true;\r
+  private boolean simulationMode = false;\r
+  private long maxExecutionTimeInMillis = 0L;\r
+\r
+  public TestInstanceCreateRequest() throws Exception {\r
+    this.validate();\r
+  }\r
+\r
+  public TestInstanceCreateRequest(\r
+      String testInstanceName,\r
+      String testInstanceDescription,\r
+      HashMap<String, ParallelFlowInput> pfloInput,\r
+      HashMap<String, Object> simulationVthInput,\r
+      HashMap<String, Object> testData,\r
+      HashMap<String, Object> vthInput,\r
+      ObjectId createdBy,\r
+      boolean useLatestTestDefinition,\r
+      boolean simulationMode,\r
+      long maxExecutionTimeInMillis) throws Exception {\r
+    this.testInstanceName = testInstanceName;\r
+    this.testInstanceDescription = testInstanceDescription;\r
+    this.pfloInput = pfloInput;\r
+    this.simulationVthInput = simulationVthInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.createdBy = createdBy;\r
+    this.useLatestTestDefinition = useLatestTestDefinition;\r
+    this.simulationMode = simulationMode;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+\r
+    this.validate();\r
+  }\r
+\r
+  private void validate() throws Exception {\r
+    String missingFieldFormat = "The field %s is required.";\r
+    if (Strings.isNullOrEmpty(testInstanceName)) {\r
+      throw new Exception(String.format(missingFieldFormat, "testInstanceName"));\r
+    }\r
+\r
+    if (Strings.isNullOrEmpty(testInstanceDescription)) {\r
+      throw new Exception(String.format(missingFieldFormat, "testInstanceDescription"));\r
+    }\r
+\r
+    if (pfloInput == null) {\r
+      pfloInput = new HashMap<>();\r
+    }\r
+\r
+    if (simulationVthInput == null) {\r
+      simulationVthInput = new HashMap<>();\r
+    }\r
+\r
+    if (testData == null) {\r
+      testData = new HashMap<>();\r
+    }\r
+\r
+    if (vthInput == null) {\r
+      vthInput = new HashMap<>();\r
+    }\r
+\r
+    if (this.maxExecutionTimeInMillis < 0L) {\r
+      this.maxExecutionTimeInMillis = 0L;\r
+    }\r
+  }\r
+\r
+  public static long getSerialVersionUID() {\r
+    return serialVersionUID;\r
+  }\r
+\r
+  public ObjectId getTestDefinitionId() {\r
+    return testDefinitionId;\r
+  }\r
+\r
+  public void setTestDefinitionId(ObjectId testDefinitionId) {\r
+    this.testDefinitionId = testDefinitionId;\r
+  }\r
+\r
+  public int getVersion() {\r
+    return version;\r
+  }\r
+\r
+  public void setVersion(int version) {\r
+    this.version = version;\r
+  }\r
+\r
+  public String getProcessDefinitionKey() {\r
+    return processDefinitionKey;\r
+  }\r
+\r
+  public void setProcessDefinitionKey(String processDefinitionKey) {\r
+    this.processDefinitionKey = processDefinitionKey;\r
+  }\r
+\r
+  public String getTestInstanceName() {\r
+    return testInstanceName;\r
+  }\r
+\r
+  public void setTestInstanceName(String testInstanceName) {\r
+    this.testInstanceName = testInstanceName;\r
+  }\r
+\r
+  public String getTestInstanceDescription() {\r
+    return testInstanceDescription;\r
+  }\r
+\r
+  public void setTestInstanceDescription(String testInstanceDescription) {\r
+    this.testInstanceDescription = testInstanceDescription;\r
+  }\r
+\r
+  public HashMap<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(HashMap<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public HashMap<String, Object> getSimulationVthInput() {\r
+    return simulationVthInput;\r
+  }\r
+\r
+  public void setSimulationVthInput(HashMap<String, Object> simulationVthInput) {\r
+    this.simulationVthInput = simulationVthInput;\r
+  }\r
+\r
+  public HashMap<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(HashMap<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public HashMap<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(HashMap<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public ObjectId getCreatedBy() {\r
+    return createdBy;\r
+  }\r
+\r
+  public void setCreatedBy(ObjectId createdBy) {\r
+    this.createdBy = createdBy;\r
+  }\r
+\r
+  public boolean isUseLatestTestDefinition() {\r
+    return useLatestTestDefinition;\r
+  }\r
+\r
+  public void setUseLatestTestDefinition(boolean useLatestTestDefinition) {\r
+    this.useLatestTestDefinition = useLatestTestDefinition;\r
+  }\r
+\r
+  public boolean isSimulationMode() {\r
+    return simulationMode;\r
+  }\r
+\r
+  public void setSimulationMode(boolean simulationMode) {\r
+    this.simulationMode = simulationMode;\r
+  }\r
+\r
+  public long getMaxExecutionTimeInMillis() {\r
+    return maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/UserGroup.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/UserGroup.java
new file mode 100644 (file)
index 0000000..536bc67
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import java.io.Serializable;\r
+import java.util.List;\r
+import org.bson.types.ObjectId;\r
+\r
+public class UserGroup implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private ObjectId groupId;\r
+  private List<String> permissions;\r
+\r
+  public UserGroup(){}\r
+  public UserGroup(ObjectId groupId, List<String> permissions) {\r
+    this.groupId = groupId;\r
+    this.permissions = permissions;\r
+  }\r
+\r
+  public ObjectId getGroupId() {\r
+    return groupId;\r
+  }\r
+\r
+  public void setGroupId(ObjectId groupId) {\r
+    this.groupId = groupId;\r
+  }\r
+\r
+  public List<String> getPermissions() {\r
+    return permissions;\r
+  }\r
+\r
+  public void setPermissions(List<String> permissions) {\r
+    this.permissions = permissions;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/model/local/WorkflowRequest.java b/otf-service-api/src/main/java/org/oran/otf/common/model/local/WorkflowRequest.java
new file mode 100644 (file)
index 0000000..f7089a0
--- /dev/null
@@ -0,0 +1,163 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.model.local;\r
+\r
+import org.oran.otf.common.utility.gson.Convert;\r
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;\r
+import java.io.Serializable;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+@JsonIgnoreProperties(ignoreUnknown = true)\r
+public class WorkflowRequest implements Serializable {\r
+\r
+  private static final long serialVersionUID = 1L;\r
+\r
+  private boolean async = false;\r
+  private ObjectId executorId = null;\r
+  private ObjectId testInstanceId = null;\r
+  private Map<String, ParallelFlowInput> pfloInput = null;\r
+  private Map<String, Object> testData = null;\r
+  private Map<String, Object> vthInput = null;\r
+  private long maxExecutionTimeInMillis = 0L;\r
+\r
+  public WorkflowRequest() throws Exception {\r
+    this.validate();\r
+  }\r
+\r
+  public WorkflowRequest(\r
+          boolean async,\r
+          ObjectId executorId,\r
+          ObjectId testInstanceId,\r
+          Map<String, ParallelFlowInput> pfloInput,\r
+          Map<String, Object> testData,\r
+          Map<String, Object> vthInput,\r
+          int maxExecutionTimeInMillis)\r
+          throws Exception {\r
+    this.async = async;\r
+    this.executorId = executorId;\r
+    this.testInstanceId = testInstanceId;\r
+    this.pfloInput = pfloInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+\r
+    this.validate();\r
+  }\r
+\r
+  public WorkflowRequest(\r
+          boolean async,\r
+          String executorId,\r
+          String testInstanceId,\r
+          Map<String, ParallelFlowInput> pfloInput,\r
+          Map<String, Object> testData,\r
+          Map<String, Object> vthInput,\r
+          int maxExecutionTimeInMillis)\r
+          throws Exception {\r
+    this.async = async;\r
+    this.executorId = new ObjectId(executorId);\r
+    this.testInstanceId = new ObjectId(testInstanceId);\r
+    this.pfloInput = pfloInput;\r
+    this.testData = testData;\r
+    this.vthInput = vthInput;\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+\r
+    this.validate();\r
+  }\r
+\r
+  private void validate() throws Exception {\r
+    String missingFieldFormat = "Missing required field %s.";\r
+    //    if (this.async && this.asyncTopic == null) {\r
+    //      throw new Exception(String.format(missingFieldFormat, "asyncTopic"));\r
+    //    }\r
+\r
+    // Only required on the Camunda engine\r
+    //    if (this.executorId == null) {\r
+    //      throw new Exception(String.format(missingFieldFormat, "executorId"));\r
+    //    }\r
+\r
+    // Only required on the Camunda engine\r
+    //    if (this.testInstanceId == null) {\r
+    //      throw new Exception(String.format(missingFieldFormat, "testInstanceId"));\r
+    //    }\r
+\r
+    if (this.maxExecutionTimeInMillis < 0L) {\r
+      this.maxExecutionTimeInMillis = 0L;\r
+    }\r
+  }\r
+\r
+  public boolean isAsync() {\r
+    return async;\r
+  }\r
+\r
+  public void setAsync(boolean async) {\r
+    this.async = async;\r
+  }\r
+\r
+  public ObjectId getExecutorId() {\r
+    return executorId;\r
+  }\r
+\r
+  public void setExecutorId(ObjectId executorId) {\r
+    this.executorId = executorId;\r
+  }\r
+\r
+  public ObjectId getTestInstanceId() {\r
+    return testInstanceId;\r
+  }\r
+\r
+  public void setTestInstanceId(ObjectId testInstanceId) {\r
+    this.testInstanceId = testInstanceId;\r
+  }\r
+\r
+  public Map<String, ParallelFlowInput> getPfloInput() {\r
+    return pfloInput;\r
+  }\r
+\r
+  public void setPfloInput(Map<String, ParallelFlowInput> pfloInput) {\r
+    this.pfloInput = pfloInput;\r
+  }\r
+\r
+  public Map<String, Object> getTestData() {\r
+    return testData;\r
+  }\r
+\r
+  public void setTestData(Map<String, Object> testData) {\r
+    this.testData = testData;\r
+  }\r
+\r
+  public Map<String, Object> getVthInput() {\r
+    return vthInput;\r
+  }\r
+\r
+  public void setVthInput(Map<String, Object> vthInput) {\r
+    this.vthInput = vthInput;\r
+  }\r
+\r
+  public long getMaxExecutionTimeInMillis() {\r
+    return maxExecutionTimeInMillis;\r
+  }\r
+\r
+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {\r
+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return Convert.objectToJson(this);\r
+  }\r
+}
\ No newline at end of file
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/repository/GroupRepository.java b/otf-service-api/src/main/java/org/oran/otf/common/repository/GroupRepository.java
new file mode 100644 (file)
index 0000000..69d000c
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+import org.springframework.data.mongodb.repository.Query;\r
+\r
+import java.util.List;\r
+\r
+public interface GroupRepository extends MongoRepository<Group, String> {\r
+    @Query("{ 'members.userId': ?0 }")\r
+    public List<Group> findAllByMembersId(ObjectId membersUserId);\r
+    public Group findFirstByGroupName(String groupName);\r
+}\r
+\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java
new file mode 100644 (file)
index 0000000..ecd2bab
--- /dev/null
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestDefinition;\r
+import java.util.Optional;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface TestDefinitionRepository extends MongoRepository<TestDefinition, String> {\r
+\r
+  Optional<TestDefinition> findByProcessDefinitionKey(String processDefinitionKey);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java
new file mode 100644 (file)
index 0000000..ee86a82
--- /dev/null
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import java.util.Optional;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface TestExecutionRepository extends MongoRepository<TestExecution, String> {\r
+\r
+  Optional<TestExecution> findFirstByProcessInstanceId(String processInstanceId);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java
new file mode 100644 (file)
index 0000000..09ab4ac
--- /dev/null
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestHead;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+import java.util.Optional;\r
+\r
+public interface TestHeadRepository extends MongoRepository<TestHead, String> {\r
+    Optional<TestHead> findByTestHeadName(String testHeadName);\r
+\r
+\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java
new file mode 100644 (file)
index 0000000..16d1dcb
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.TestInstance;\r
+import java.util.List;\r
+import java.util.Optional;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+import org.springframework.data.mongodb.repository.Query;\r
+\r
+public interface TestInstanceRepository extends MongoRepository<TestInstance, String> {\r
+\r
+  Optional<TestInstance> findByTestInstanceName(String testInstanceName);\r
+\r
+  @Query("{ 'testDefinitionId': ?0 }")\r
+  List<TestInstance> findAllByTestDefinitionId(ObjectId testDefinitionId);\r
+\r
+  @Query("{ 'testDefinitionId': ?0, 'processDefinitionId': ?1 }")\r
+  List<TestInstance> findAllByTestDefinitionIdAndPDId(\r
+      ObjectId testDefinitionId, String processDefinitionId);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/repository/UserRepository.java b/otf-service-api/src/main/java/org/oran/otf/common/repository/UserRepository.java
new file mode 100644 (file)
index 0000000..5dd669f
--- /dev/null
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.repository;\r
+\r
+import org.oran.otf.common.model.User;\r
+import java.util.Optional;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public interface UserRepository extends MongoRepository<User, String> {\r
+  Optional<User> findFirstByEmail(String email);\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/util/HttpUtils.java b/otf-service-api/src/main/java/org/oran/otf/common/util/HttpUtils.java
new file mode 100644 (file)
index 0000000..b5e3a39
--- /dev/null
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.util;\r
+\r
+public class HttpUtils {}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/RSAEncryptDecrypt.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/RSAEncryptDecrypt.java
new file mode 100644 (file)
index 0000000..1309d6d
--- /dev/null
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility;\r
+\r
+import java.security.KeyPair;\r
+import java.security.KeyPairGenerator;\r
+import java.security.NoSuchAlgorithmException;\r
+import javax.crypto.Cipher;\r
+import org.springframework.stereotype.Service;\r
+\r
+@Service\r
+public class RSAEncryptDecrypt {\r
+\r
+  private KeyPair keyPair;\r
+\r
+  public RSAEncryptDecrypt() throws NoSuchAlgorithmException {\r
+    this.keyPair = buildKeyPair();\r
+  }\r
+\r
+  private KeyPair buildKeyPair() throws NoSuchAlgorithmException {\r
+    final int keySize = 2048;\r
+    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");\r
+    keyPairGenerator.initialize(keySize);\r
+    return keyPairGenerator.genKeyPair();\r
+  }\r
+\r
+  public byte[] encrypt(String message) throws Exception {\r
+    Cipher cipher = Cipher.getInstance("RSA");\r
+    cipher.init(Cipher.ENCRYPT_MODE, this.keyPair.getPrivate());\r
+\r
+    return cipher.doFinal(message.getBytes());\r
+  }\r
+\r
+  public byte[] decrypt(byte[] encrypted) throws Exception {\r
+    Cipher cipher = Cipher.getInstance("RSA");\r
+    cipher.init(Cipher.DECRYPT_MODE, this.keyPair.getPublic());\r
+\r
+    return cipher.doFinal(encrypted);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/Utility.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/Utility.java
new file mode 100644 (file)
index 0000000..c781ffb
--- /dev/null
@@ -0,0 +1,84 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility;\r
+\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import com.google.common.base.Strings;\r
+\r
+import java.lang.reflect.Field;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.UUID;\r
+\r
+public class Utility {\r
+\r
+  public static String getLoggerPrefix() {\r
+    return "[" + Thread.currentThread().getStackTrace()[2].getMethodName() + "]: ";\r
+  }\r
+\r
+  public static Map<?, ?> toMap(Object obj) throws Exception {\r
+    ObjectMapper mapper = new ObjectMapper();\r
+    return mapper.convertValue(obj, HashMap.class);\r
+  }\r
+\r
+  public static boolean isCollection(Object obj) {\r
+    return obj.getClass().isArray() || obj instanceof Collection;\r
+  }\r
+\r
+  public static List<?> toList(Object obj) {\r
+    if (obj == null) {\r
+      throw new NullPointerException("Argument cannot be null.");\r
+    }\r
+\r
+    List<?> list = new ArrayList<>();\r
+    if (obj.getClass().isArray()) {\r
+      list = Arrays.asList((Object[]) obj);\r
+    } else if (obj instanceof Collection) {\r
+      list = new ArrayList<>((Collection<?>) obj);\r
+    }\r
+\r
+    return list;\r
+  }\r
+\r
+  public static boolean isValidUuid(String str) {\r
+    if (Strings.isNullOrEmpty(str)) {\r
+      return false;\r
+    }\r
+    try {\r
+      UUID uuid = UUID.fromString(str);\r
+      return uuid.toString().equalsIgnoreCase(str);\r
+    } catch (IllegalArgumentException iae) {\r
+      return false;\r
+    }\r
+  }\r
+\r
+  // check a name type pair to see if it matches field in class\r
+  public static boolean isTypeVariablePairInClass(String variableName, Object variableValue, Class javaClass){\r
+    List<Field> testHeadFields = Arrays.asList(javaClass.getFields());\r
+    for(int i = 0; i < testHeadFields.size(); i++){\r
+      Field field = testHeadFields.get(i);\r
+      if(field.getName().equals(variableName) && field.getType().isInstance(variableValue)){\r
+        return true;\r
+      }\r
+    }\r
+    return false;\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/database/Generic.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/database/Generic.java
new file mode 100644 (file)
index 0000000..5de5043
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.database;\r
+\r
+import java.util.Optional;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.repository.MongoRepository;\r
+\r
+public class Generic {\r
+\r
+  public static <T> boolean identifierExistsInCollection(\r
+      MongoRepository<T, String> repository, ObjectId identifier) {\r
+    return repository.findById(identifier.toString()).isPresent();\r
+  }\r
+\r
+  public static <T> T findByIdGeneric(MongoRepository<T, String> repository, ObjectId identifier) {\r
+    Optional<T> optionalObj = repository.findById(identifier.toString());\r
+    return optionalObj.orElse(null);\r
+  }\r
+\r
+\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java
new file mode 100644 (file)
index 0000000..c54359f
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.database;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import com.mongodb.client.result.UpdateResult;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.data.mongodb.core.query.Update;\r
+\r
+public class TestExecutionUtility {\r
+\r
+  public static void saveTestResult(\r
+      MongoTemplate mongoOperation, TestExecution execution, String testResult) {\r
+    Query query = new Query();\r
+    query.addCriteria(Criteria.where("businessKey").is(execution.getBusinessKey()));\r
+    Update update = new Update();\r
+    update.set("testResult", testResult);\r
+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/gson/Convert.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/gson/Convert.java
new file mode 100644 (file)
index 0000000..bc1d0af
--- /dev/null
@@ -0,0 +1,95 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.gson;\r
+\r
+import com.fasterxml.jackson.core.type.TypeReference;\r
+import com.fasterxml.jackson.databind.DeserializationFeature;\r
+import com.fasterxml.jackson.databind.ObjectMapper;\r
+import com.google.gson.Gson;\r
+import com.google.gson.GsonBuilder;\r
+import com.google.gson.JsonDeserializationContext;\r
+import com.google.gson.JsonDeserializer;\r
+import com.google.gson.JsonElement;\r
+import com.google.gson.JsonParseException;\r
+import com.google.gson.JsonPrimitive;\r
+import com.google.gson.JsonSerializationContext;\r
+import com.google.gson.JsonSerializer;\r
+import com.google.gson.reflect.TypeToken;\r
+\r
+import java.io.IOException;\r
+import java.lang.reflect.Type;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+public class Convert {\r
+\r
+  private static final GsonBuilder gsonBuilder =\r
+      new GsonBuilder()\r
+          .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")\r
+          .registerTypeAdapter(\r
+              ObjectId.class,\r
+              new JsonSerializer<ObjectId>() {\r
+                @Override\r
+                public JsonElement serialize(\r
+                    ObjectId src, Type typeOfSrc, JsonSerializationContext context) {\r
+                  return new JsonPrimitive(src.toHexString());\r
+                }\r
+              })\r
+          .registerTypeAdapter(\r
+              ObjectId.class,\r
+              new JsonDeserializer<ObjectId>() {\r
+                @Override\r
+                public ObjectId deserialize(\r
+                    JsonElement json, Type typeOfT, JsonDeserializationContext context)\r
+                    throws JsonParseException {\r
+                  return new ObjectId(json.getAsString());\r
+                }\r
+              });\r
+\r
+  public static Gson getGson() {\r
+    return gsonBuilder.create();\r
+  }\r
+\r
+  public static String mapToJson(Map map) {\r
+    if (map.isEmpty()) {\r
+      return "{}";\r
+    }\r
+    return getGson().toJson(map);\r
+  }\r
+\r
+  public static Map<String, Object> jsonToMap(String json) {\r
+    Type type = new TypeToken<HashMap<String, Object>>() {\r
+    }.getType();\r
+    return getGson().fromJson(json, type);\r
+  }\r
+\r
+  public static String objectToJson(Object obj) {\r
+    return getGson().toJson(obj);\r
+  }\r
+\r
+  public static<T> T mapToObject(Map map, TypeReference<T> typeReference) throws IOException {\r
+    return jsonToObject(mapToJson(map), typeReference);\r
+  }\r
+\r
+  public static <T> T jsonToObject(String json, TypeReference<T> typeReference) throws IOException {\r
+    ObjectMapper objectMapper = new ObjectMapper();\r
+    objectMapper\r
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);\r
+    return objectMapper.readValue(json, typeReference);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/gson/GsonUtils.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/gson/GsonUtils.java
new file mode 100644 (file)
index 0000000..1b224fc
--- /dev/null
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.gson;\r
+\r
+import com.google.gson.Gson;\r
+import com.google.gson.GsonBuilder;\r
+import com.google.gson.JsonDeserializationContext;\r
+import com.google.gson.JsonDeserializer;\r
+import com.google.gson.JsonElement;\r
+import com.google.gson.JsonParseException;\r
+import com.google.gson.JsonPrimitive;\r
+import com.google.gson.JsonSerializationContext;\r
+import com.google.gson.JsonSerializer;\r
+import com.google.gson.reflect.TypeToken;\r
+import java.lang.reflect.Type;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import org.bson.types.ObjectId;\r
+\r
+public class GsonUtils {\r
+    private static final GsonBuilder gsonBuilder =\r
+            new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")\r
+                    .registerTypeAdapter(ObjectId.class, new JsonSerializer<ObjectId>() {\r
+                        @Override\r
+                        public JsonElement serialize(ObjectId src, Type typeOfSrc,\r
+                                                     JsonSerializationContext context) {\r
+                            return new JsonPrimitive(src.toHexString());\r
+                        }\r
+                    }).registerTypeAdapter(ObjectId.class, new JsonDeserializer<ObjectId>() {\r
+                @Override\r
+                public ObjectId deserialize(JsonElement json, Type typeOfT,\r
+                                            JsonDeserializationContext context) throws JsonParseException {\r
+                    return new ObjectId(json.getAsString());\r
+                }\r
+            });\r
+\r
+    public static Gson getGson() {\r
+        return gsonBuilder.create();\r
+    }\r
+\r
+    private static final Gson gson = getGson();\r
+    private static final Type TT_mapStringString = new TypeToken<Map<String,String>>(){}.getType();\r
+\r
+    public static Map<String, String> jsonToMapStringString(String json) {\r
+        Map<String, String> ret = new HashMap<String, String>();\r
+        if (json == null || json.isEmpty())\r
+            return ret;\r
+        return gson.fromJson(json, TT_mapStringString);\r
+    }\r
+    public static String mapStringObjectToJson(Map<String, Object> map) {\r
+        if (map == null)\r
+            map = new HashMap<String, Object>();\r
+        return gson.toJson(map);\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java
new file mode 100644 (file)
index 0000000..2af3f90
--- /dev/null
@@ -0,0 +1,160 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.http;\r
+\r
+import com.google.common.base.Strings;\r
+import java.io.IOException;\r
+import java.io.UnsupportedEncodingException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+import java.util.concurrent.ExecutionException;\r
+import java.util.concurrent.Future;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.client.methods.HttpGet;\r
+import org.apache.http.client.methods.HttpPost;\r
+import org.apache.http.client.methods.HttpRequestBase;\r
+import org.apache.http.entity.StringEntity;\r
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;\r
+import org.apache.http.impl.nio.client.HttpAsyncClients;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+\r
+public class RequestUtility {\r
+\r
+  private static Logger logger = LoggerFactory.getLogger(RequestUtility.class);\r
+\r
+  public static void postAsync(String url, String body, Map<String, String> headers)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    HttpPost post = buildPost(url, body, headers);\r
+    executeAsync(post);\r
+  }\r
+\r
+  public static HttpResponse postSync(String url, String body, Map<String, String> headers)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    HttpPost post = buildPost(url, body, headers);\r
+    return executeSync(post);\r
+  }\r
+\r
+  public static HttpResponse postSync(\r
+      String url, String body, Map<String, String> headers, int timeoutInMillis)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    HttpPost post = buildPost(url, body, headers);\r
+    return executeSync(post, timeoutInMillis);\r
+  }\r
+\r
+  public static HttpResponse getSync(String url, Map<String, String> headers)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    HttpGet get = buildGet(url, headers);\r
+    return executeSync(get);\r
+  }\r
+\r
+  public static HttpResponse getSync(String url, Map<String, String> headers, int timeoutInMillis)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    if (timeoutInMillis < 0) {\r
+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");\r
+    }\r
+\r
+    HttpGet get = buildGet(url, headers);\r
+    return executeSync(get, timeoutInMillis);\r
+  }\r
+\r
+  public static void getAsync(String url, Map<String, String> headers) throws IOException {\r
+    HttpGet get = buildGet(url, headers);\r
+    executeAsync(get);\r
+  }\r
+\r
+  private static HttpPost buildPost(String url, String body, Map<String, String> headers)\r
+      throws UnsupportedEncodingException {\r
+    if (Strings.isNullOrEmpty(url) || Strings.isNullOrEmpty(body)) {\r
+      return null;\r
+    } else if (headers == null) {\r
+      headers = new HashMap<>();\r
+    }\r
+\r
+    HttpPost post = new HttpPost(url);\r
+    headers.forEach(post::setHeader);\r
+    post.setEntity(new StringEntity(body));\r
+    return post;\r
+  }\r
+\r
+  private static HttpGet buildGet(String url, Map<String, String> headers) {\r
+    if (Strings.isNullOrEmpty(url)) {\r
+      return null;\r
+    } else if (headers == null) {\r
+      headers = new HashMap<>();\r
+    }\r
+\r
+    HttpGet get = new HttpGet(url);\r
+    headers.forEach(get::setHeader);\r
+    return get;\r
+  }\r
+\r
+  private static HttpResponse executeSync(HttpRequestBase request)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();\r
+    try {\r
+      httpClient.start();\r
+      Future<HttpResponse> future = httpClient.execute(request, null);\r
+      return future.get();\r
+    } finally {\r
+      httpClient.close();\r
+    }\r
+  }\r
+\r
+  private static HttpResponse executeSync(HttpRequestBase request, int timeoutInMillis)\r
+      throws IOException, InterruptedException, ExecutionException {\r
+    if (timeoutInMillis < 0) {\r
+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");\r
+    }\r
+\r
+    // Create a timer task that will abort the task (the request) after the specified time. This\r
+    // task will run *timeoutInMillis* ms\r
+    TimerTask task =\r
+        new TimerTask() {\r
+          @Override\r
+          public void run() {\r
+            if (request != null) {\r
+              request.abort();\r
+            }\r
+          }\r
+        };\r
+\r
+    CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();\r
+    try {\r
+      httpClient.start();\r
+      // Start the timer before making the request.\r
+      new Timer(true).schedule(task, timeoutInMillis);\r
+      Future<HttpResponse> future = httpClient.execute(request, null);\r
+      return future.get();\r
+    } finally {\r
+      httpClient.close();\r
+    }\r
+  }\r
+\r
+  private static void executeAsync(HttpRequestBase request) throws IOException {\r
+    CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();\r
+    try {\r
+      httpClient.start();\r
+      httpClient.execute(request, null);\r
+      logger.debug("Sent asynchronous request.");\r
+    } finally {\r
+      httpClient.close();\r
+    }\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java
new file mode 100644 (file)
index 0000000..919897c
--- /dev/null
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.http;\r
+\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import javax.ws.rs.core.MediaType;\r
+import javax.ws.rs.core.Response;\r
+\r
+public class ResponseUtility {\r
+\r
+  public static class Build {\r
+\r
+    public static Response okRequest() {\r
+      return Response.ok().build();\r
+    }\r
+\r
+    public static Response badRequest() {\r
+      return Response.status(400).build();\r
+    }\r
+\r
+    public static Response okRequestWithMessage(String msg) {\r
+      return Response.status(200)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(200, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response okRequestWithObject(Object obj) {\r
+      return Response.status(200).type(MediaType.APPLICATION_JSON).entity(obj).build();\r
+    }\r
+\r
+    public static Response badRequestWithMessage(String msg) {\r
+      return Response.status(400)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(400, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response internalServerError() {\r
+      return Response.status(500).build();\r
+    }\r
+\r
+    public static Response internalServerErrorWithMessage(String msg) {\r
+      return Response.status(500)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(500, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response unauthorized() {\r
+      return Response.status(401).build();\r
+    }\r
+    public static Response unauthorizedWithMessage(String msg) {\r
+      return Response.status(401)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(401, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response notFoundWithMessage(String msg) {\r
+      return Response.status(404)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(404, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response serviceUnavailableWithMessage(String msg) {\r
+      return Response.status(503)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(503, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response serviceUnavailable() {\r
+      return Response.status(503).build();\r
+    }\r
+\r
+    public static Response genericWithCode(int code) {\r
+      return Response.status(code).build();\r
+    }\r
+\r
+    public static Response genericWithMessage(int code, String msg) {\r
+      return Response.status(code)\r
+          .type(MediaType.APPLICATION_JSON)\r
+          .entity(new OTFApiResponse(code, msg))\r
+          .build();\r
+    }\r
+\r
+    public static Response genericWithObject(int code, Object obj) {\r
+      return Response.status(code).type(MediaType.APPLICATION_JSON).entity(obj).build();\r
+    }\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java
new file mode 100644 (file)
index 0000000..8327a81
--- /dev/null
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.logger;\r
+\r
+public enum ErrorCode {\r
+  PermissionError(100),\r
+  AvailabilityError(200),\r
+  DataError(300),\r
+  SchemaError(400),\r
+  BusinessProcesssError(500),\r
+  UnknownError(900);\r
+\r
+  private int value;\r
+\r
+  ErrorCode(int value) {\r
+    this.value = value;\r
+  }\r
+\r
+  public int getValue() {\r
+    return this.value;\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java
new file mode 100644 (file)
index 0000000..10c45d8
--- /dev/null
@@ -0,0 +1,91 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * ================================================================================\r
+ * Modifications Copyright (c) 2019 Samsung\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.common.utility.logger;\r
+\r
+import ch.qos.logback.classic.Level;\r
+import ch.qos.logback.classic.LoggerContext;\r
+import ch.qos.logback.classic.spi.LoggerContextListener;\r
+import ch.qos.logback.core.Context;\r
+import ch.qos.logback.core.spi.ContextAwareBase;\r
+import ch.qos.logback.core.spi.LifeCycle;\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.springframework.stereotype.Component;\r
+\r
+@Component\r
+public class LoggerStartupListener extends ContextAwareBase\r
+    implements LoggerContextListener, LifeCycle {\r
+\r
+  private static final Logger logger = LoggerFactory.getLogger(LoggerStartupListener.class);\r
+  private boolean started = false;\r
+\r
+  @Override\r
+  public void start() {\r
+    if (started) {\r
+      return;\r
+    }\r
+    InetAddress addr = null;\r
+    try {\r
+      addr = InetAddress.getLocalHost();\r
+    } catch (UnknownHostException e) {\r
+      logger.error("UnknownHostException", e);\r
+    }\r
+    Context context = getContext();\r
+    if (addr != null) {\r
+      context.putProperty("server.name", addr.getHostName());\r
+    }\r
+    started = true;\r
+  }\r
+\r
+  @Override\r
+  public void stop() {\r
+  }\r
+\r
+  @Override\r
+  public boolean isStarted() {\r
+    return started;\r
+  }\r
+\r
+  @Override\r
+  public boolean isResetResistant() {\r
+    return true;\r
+  }\r
+\r
+  @Override\r
+  public void onReset(LoggerContext arg0) {\r
+  }\r
+\r
+  @Override\r
+  public void onStart(LoggerContext arg0) {\r
+  }\r
+\r
+  @Override\r
+  public void onStop(LoggerContext arg0) {\r
+  }\r
+\r
+  @Override\r
+  public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {\r
+  }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java
new file mode 100644 (file)
index 0000000..1103c53
--- /dev/null
@@ -0,0 +1,35 @@
+/*-\r
+ * ============LICENSE_START=======================================================\r
+ * ONAP - SO\r
+ * ================================================================================\r
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.oran.otf.common.utility.logger;\r
+\r
+\r
+public enum MessageEnum {\r
+  // Api Handler Messages\r
+  APIH_REQUEST_NULL, APIH_QUERY_FOUND, APIH_QUERY_NOT_FOUND, APIH_QUERY_PARAM_WRONG, APIH_DB_ACCESS_EXC, APIH_DB_ACCESS_EXC_REASON, APIH_VALIDATION_ERROR, APIH_REQUEST_VALIDATION_ERROR, APIH_SERVICE_VALIDATION_ERROR, APIH_GENERAL_EXCEPTION_ARG, APIH_GENERAL_EXCEPTION, APIH_GENERAL_WARNING, APIH_AUDIT_EXEC, APIH_GENERAL_METRICS, APIH_DUPLICATE_CHECK_EXC, APIH_DUPLICATE_FOUND, APIH_BAD_ORDER, APIH_DB_ATTRIBUTE_NOT_FOUND, APIH_BPEL_COMMUNICATE_ERROR, APIH_BPEL_RESPONSE_ERROR, APIH_WARP_REQUEST, APIH_ERROR_FROM_BPEL_SERVER, APIH_DB_INSERT_EXC, APIH_DB_UPDATE_EXC, APIH_NO_PROPERTIES, APIH_PROPERTY_LOAD_SUC, APIH_LOAD_PROPERTIES_FAIL, APIH_SDNC_COMMUNICATE_ERROR, APIH_SDNC_RESPONSE_ERROR, APIH_CANNOT_READ_SCHEMA, APIH_HEALTH_CHECK_EXCEPTION, APIH_REQUEST_VALIDATION_ERROR_REASON, APIH_JAXB_MARSH_ERROR, APIH_JAXB_UNMARSH_ERROR, APIH_VNFREQUEST_VALIDATION_ERROR, APIH_DOM2STR_ERROR, APIH_READ_VNFOUTPUT_CLOB_EXCEPTION, APIH_DUPLICATE_CHECK_EXC_ATT, APIH_GENERATED_REQUEST_ID, APIH_GENERATED_SERVICE_INSTANCE_ID, APIH_REPLACE_REQUEST_ID,\r
+  // Resource Adapter Messages\r
+  RA_GENERAL_EXCEPTION_ARG, RA_GENERAL_EXCEPTION, RA_GENERAL_WARNING, RA_MISSING_PARAM, RA_AUDIT_EXEC, RA_GENERAL_METRICS, RA_CREATE_STACK_TIMEOUT, RA_DELETE_STACK_TIMEOUT, RA_UPDATE_STACK_TIMEOUT, RA_CONNECTION_EXCEPTION, RA_PARSING_ERROR, RA_PROPERTIES_NOT_FOUND, RA_LOAD_PROPERTIES_SUC, RA_NETWORK_ALREADY_EXIST, RA_UPDATE_NETWORK_ERR, RA_CREATE_STACK_ERR, RA_UPDATE_STACK_ERR, RA_CREATE_TENANT_ERR, RA_NETWORK_NOT_FOUND, RA_NETWORK_ORCHE_MODE_NOT_SUPPORT, RA_CREATE_NETWORK_EXC, RA_NS_EXC, RA_PARAM_NOT_FOUND, RA_CONFIG_EXC, RA_UNKOWN_PARAM, RA_VLAN_PARSE, RA_DELETE_NETWORK_EXC, RA_ROLLBACK_NULL, RA_TENANT_NOT_FOUND, RA_QUERY_NETWORK_EXC, RA_CREATE_NETWORK_NOTIF_EXC, RA_ASYNC_ROLLBACK, RA_WSDL_NOT_FOUND, RA_WSDL_URL_CONVENTION_EXC, RA_INIT_NOTIF_EXC, RA_SET_CALLBACK_AUTH_EXC, RA_FAULT_INFO_EXC, RA_MARSHING_ERROR, RA_PARSING_REQUEST_ERROR, RA_SEND_REQUEST_SDNC, RA_RESPONSE_FROM_SDNC, RA_EXCEPTION_COMMUNICATE_SDNC, RA_EVALUATE_XPATH_ERROR, RA_ANALYZE_ERROR_EXC, RA_ERROR_GET_RESPONSE_SDNC, RA_CALLBACK_BPEL, RA_INIT_CALLBACK_WSDL_ERR, RA_CALLBACK_BPEL_EXC, RA_CALLBACK_BPEL_COMPLETE, RA_SDNC_MISS_CONFIG_PARAM, RA_SDNC_INVALID_CONFIG, RA_PRINT_URL, RA_ERROR_CREATE_SDNC_REQUEST, RA_ERROR_CREATE_SDNC_RESPONSE, RA_ERROR_CONVERT_XML2STR, RA_RECEIVE_SDNC_NOTIF, RA_INIT_SDNC_ADAPTER, RA_SEND_REQUEST_APPC_ERR, RA_SEND_REQUEST_SDNC_ERR, RA_RECEIVE_BPEL_REQUEST, RA_TENANT_ALREADY_EXIST, RA_UPDATE_TENANT_ERR, RA_DELETE_TEMAMT_ERR, RA_ROLLBACK_TENANT_ERR, RA_QUERY_VNF_ERR, RA_VNF_ALREADY_EXIST, RA_VNF_UNKNOWN_PARAM, RA_VNF_EXTRA_PARAM, RA_CREATE_VNF_ERR, RA_VNF_NOT_EXIST, RA_UPDATE_VNF_ERR, RA_DELETE_VNF_ERR, RA_ASYNC_CREATE_VNF, RA_SEND_VNF_NOTIF_ERR, RA_ASYNC_CREATE_VNF_COMPLETE, RA_ASYNC_UPDATE_VNF, RA_ASYNC_UPDATE_VNF_COMPLETE, RA_ASYNC_QUERY_VNF, RA_ASYNC_QUERY_VNF_COMPLETE, RA_ASYNC_DELETE_VNF, RA_ASYNC_DELETE_VNF_COMPLETE, RA_ASYNC_ROLLBACK_VNF, RA_ASYNC_ROLLBACK_VNF_COMPLETE, RA_ROLLBACK_VNF_ERR, RA_DB_INVALID_STATUS, RA_CANT_UPDATE_REQUEST, RA_DB_REQUEST_NOT_EXIST, RA_CONFIG_NOT_FOUND, RA_CONFIG_LOAD, RA_RECEIVE_WORKFLOW_MESSAGE,\r
+  // BPEL engine Messages\r
+  BPMN_GENERAL_INFO, BPMN_GENERAL_EXCEPTION_ARG, BPMN_GENERAL_EXCEPTION, BPMN_GENERAL_WARNING, BPMN_AUDIT_EXEC, BPMN_GENERAL_METRICS, BPMN_URN_MAPPING_FAIL, BPMN_VARIABLE_NULL, BPMN_CALLBACK_EXCEPTION,\r
+  // ASDC Messages\r
+  ASDC_GENERAL_EXCEPTION_ARG, ASDC_GENERAL_EXCEPTION, ASDC_GENERAL_WARNING, ASDC_GENERAL_INFO, ASDC_AUDIT_EXEC, ASDC_GENERAL_METRICS, ASDC_CREATE_SERVICE, ASDC_ARTIFACT_ALREADY_DEPLOYED, ASDC_CREATE_ARTIFACT, ASDC_ARTIFACT_INSTALL_EXC, ASDC_ARTIFACT_ALREADY_DEPLOYED_DETAIL, ASDC_ARTIFACT_NOT_DEPLOYED_DETAIL, ASDC_ARTIFACT_CHECK_EXC, ASDC_INIT_ASDC_CLIENT_EXC, ASDC_INIT_ASDC_CLIENT_SUC, ASDC_LOAD_ASDC_CLIENT_EXC, ASDC_SINGLETON_CHECKT_EXC, ASDC_SHUTDOWN_ASDC_CLIENT_EXC, ASDC_CHECK_HEAT_TEMPLATE, ASDC_START_INSTALL_ARTIFACT, ASDC_ARTIFACT_TYPE_NOT_SUPPORT, ASDC_ARTIFACT_ALREADY_EXIST, ASDC_ARTIFACT_DOWNLOAD_SUC, ASDC_ARTIFACT_DOWNLOAD_FAIL, ASDC_START_DEPLOY_ARTIFACT, ASDC_SEND_NOTIF_ASDC, ASDC_SEND_NOTIF_ASDC_EXEC, ASDC_RECEIVE_CALLBACK_NOTIF, ASDC_RECEIVE_SERVICE_NOTIF, ASDC_ARTIFACT_NULL, ASDC_SERVICE_NOT_SUPPORT, ASDC_ARTIFACT_DEPLOY_SUC, ASDC_PROPERTIES_NOT_FOUND, ASDC_PROPERTIES_LOAD_SUCCESS,\r
+  // Default Messages, in case Log catalog is not defined\r
+  GENERAL_EXCEPTION_ARG, GENERAL_EXCEPTION, GENERAL_WARNING, AUDIT_EXEC, GENERAL_METRICS, LOGGER_SETUP, LOGGER_NOT_FOUND, LOGGER_UPDATE_SUC, LOGGER_UPDATE_DEBUG, LOGGER_UPDATE_DEBUG_SUC, LOAD_PROPERTIES_SUC, NO_PROPERTIES, MADATORY_PARAM_MISSING, LOAD_PROPERTIES_FAIL, INIT_LOGGER, INIT_LOGGER_FAIL, JAXB_EXCEPTION, IDENTITY_SERVICE_NOT_FOUND\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java
new file mode 100644 (file)
index 0000000..e1749bb
--- /dev/null
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.permissions;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+\r
+import java.util.Collection;\r
+\r
+public class PermissionChecker {\r
+    //check is a user have a certain permission in a group\r
+    public static boolean hasPermissionTo(User user,Group group,String permission, GroupRepository groupRepository){\r
+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);\r
+        return hasPermissionTo(userPermission,group,permission);\r
+    }\r
+    public static boolean hasPermissionTo(User user, Group group, Collection<String> permissions, GroupRepository groupRepository){\r
+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);\r
+        for(String permission : permissions){\r
+            if(!hasPermissionTo(userPermission,group,permission)){\r
+                return false;\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+    // check a users list of permission in a group\r
+    private static boolean hasPermissionTo(UserPermission userPermission, Group group,String permission){\r
+        switch (permission.toUpperCase()) {\r
+            case (UserPermission.Permission.READ):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.READ);\r
+            case (UserPermission.Permission.WRITE):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.WRITE);\r
+            case (UserPermission.Permission.EXECUTE):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.EXECUTE);\r
+            case (UserPermission.Permission.DELETE):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.DELETE);\r
+            case (UserPermission.Permission.MANAGEMENT):\r
+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.MANAGEMENT);\r
+            default:\r
+                return false;// reaches here when permission provided is not an option\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java
new file mode 100644 (file)
index 0000000..e8cdfea
--- /dev/null
@@ -0,0 +1,237 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.permissions;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.GroupMember;\r
+import org.oran.otf.common.model.Role;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+\r
+import java.util.*;\r
+\r
+public class PermissionUtil {\r
+    //build userPermission object which contains all access control information of the user\r
+    public UserPermission buildUserPermission(User user, GroupRepository groupRepository) {\r
+        UserPermission userPermission = new UserPermission();\r
+        userPermission.setUser(user);\r
+        Map<String,Set<String>> userAccessMap; // map from group to permission that user have in that group\r
+\r
+        userAccessMap = mapGroupsToPermission(user,groupRepository);\r
+        userPermission.setUserAccessMap(userAccessMap);\r
+        return userPermission;\r
+    }\r
+    // return if user have specified permission in a certain group\r
+    // ***********only use this groups that the user is in directly (non-child and non parents)****************\r
+    public static boolean hasPermissionTo (String permission,User user, Group group) {\r
+        Set<String> possiblePermissions= getUserGroupPermissions(user,group);\r
+        return possiblePermissions.stream().anyMatch(p-> p.equalsIgnoreCase(permission)); //\r
+    }\r
+    // Get all the permissions the user have in a certain group\r
+    public static Set<String> getUserGroupPermissions(User user, Group group){\r
+        Set<String> permissionsAllowed = new HashSet<>();\r
+        Set<String> usersAssignedRoles = findUserRoles(user,group);\r
+        if(usersAssignedRoles.isEmpty()) // empty set permissions because the user have no roles in the group aka not a member\r
+            return permissionsAllowed;\r
+        //get every single permissions for each role that the user have.\r
+        for(String role : usersAssignedRoles){\r
+             permissionsAllowed.addAll(getRolePermissions(role,group));\r
+        }\r
+        return permissionsAllowed;\r
+    }\r
+    //get the permissions associated with the userRoleName in group\r
+    public static Set<String> getRolePermissions(String userRoleName,Group group)\r
+    {\r
+        for(Role role : group.getRoles())\r
+        {\r
+            if(role.getRoleName().equalsIgnoreCase(userRoleName))\r
+            {\r
+                return new HashSet<String>(role.getPermissions());\r
+            }\r
+        }\r
+        return new HashSet<String>(); // empty string set if the role name cant be found in the group\r
+    }\r
+    // find the user's role in the specified group\r
+    public static Set<String> findUserRoles(User user,Group group){\r
+        for(GroupMember member : group.getMembers())\r
+        {\r
+            // if userId matches then get all the user's role in the group\r
+            if(member.getUserId().toString().equals(user.get_id().toString()))\r
+                return new HashSet<String>(member.getRoles());\r
+        }\r
+        return new HashSet<String>(); //if user have no roles\r
+    }\r
+    // create map that where key is the group id and value = users permission (string) that that group\r
+    private Map<String,Set<String>> mapGroupsToPermission(User user, GroupRepository groupRepository){\r
+        Map<String,Set<String>> groupAccessMap = new HashMap<>();\r
+        List<Group> enrolledGroups = groupRepository.findAllByMembersId(user.get_id());// enrolledGroups = groups that user is a member of\r
+        Map<String,Group> allGroupMap = groupListToMap(groupRepository.findAll());\r
+        // get all permission in the groups the user is ia member of\r
+        for(Group group: enrolledGroups) {\r
+            Set<String> permissions = getUserGroupPermissions(user,group);\r
+            groupAccessMap.put(group.get_id().toString(),convertPermissions(permissions));\r
+        }\r
+        //assign add read to all parent groups\r
+        Set<String> parentGroupsId = getParentGroups(enrolledGroups,allGroupMap);\r
+        for(String parentId : parentGroupsId)\r
+        {\r
+            // if parent access role already exist in\r
+            // group access map cause they are a member\r
+            if(groupAccessMap.get(parentId)!= null)\r
+                groupAccessMap.get(parentId).add(UserPermission.Permission.READ);\r
+            else\r
+                groupAccessMap.put(parentId,new HashSet<String>(Arrays.asList(UserPermission.Permission.READ)));\r
+        }\r
+        // if there is management role\r
+        // then assign read access to children\r
+        if(hasManagementRole(user,enrolledGroups)){\r
+//            Set<String>childIds = getChildrenGroupsId(enrolledGroups,allGroupMap,user);\r
+            for(Group enrolledGroup : enrolledGroups) {\r
+                // if enrolled groups is a management group\r
+                if(hasPermissionTo(UserPermission.Permission.MANAGEMENT,user,enrolledGroup)){\r
+                    // if there is management role then get all the child of that group, do this for all management groups\r
+                    Set<String> childIds= getChildrenGroupsId(Arrays.asList(enrolledGroup),allGroupMap,user);\r
+                    Set<String> userGroupPermissions = convertPermissions(getUserGroupPermissions(user,enrolledGroup));\r
+                    for(String childId : childIds){\r
+                        if (groupAccessMap.get(childId) != null)\r
+                            groupAccessMap.get(childId).addAll(userGroupPermissions);\r
+                        else{\r
+                            groupAccessMap.put(childId,userGroupPermissions);\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return groupAccessMap;\r
+    }\r
+    // check is user have managementRole\r
+    private boolean hasManagementRole(User user, List<Group> enrolledGroups)\r
+    {\r
+        for(Group group: enrolledGroups){\r
+            if(hasPermissionTo(UserPermission.Permission.MANAGEMENT,user,group))\r
+            {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+    // get the parent groups starting from the enrolled group of the user\r
+    private Set<String> getParentGroups(List<Group> enrolledGroup,Map<String,Group> groupMap )\r
+    {\r
+        Set<String> parentGroups = new HashSet<>();\r
+        return lookUp(enrolledGroup,groupMap,parentGroups);\r
+    }\r
+    //recursive lookup starting at the enrolled groups that the user is a member of\r
+    private Set<String> lookUp(List<Group> groupsToCheck, Map<String,Group> groupMap,Set<String> resultSet)\r
+    {\r
+        //base case: nothing to check anymore\r
+        if(groupsToCheck.isEmpty())\r
+            return resultSet;\r
+        //This is the parents directly above the current groups that are being checked\r
+        List<Group> currentParentGroups = new ArrayList<>();\r
+\r
+        for(Group group : groupsToCheck)\r
+        {\r
+            if(group.getParentGroupId() != null) // if there is a parent\r
+            {\r
+                String parentId = group.getParentGroupId().toString();\r
+                Group parentGroup = groupMap.get(parentId);\r
+                resultSet.add(parentId);\r
+                currentParentGroups.add(parentGroup); // add to currentParentGroup so it can be used recursively check for more parents\r
+            }\r
+        }\r
+        return lookUp(currentParentGroups,groupMap,resultSet);\r
+    }\r
+    // convert a list of groups to a map of group ids to group\r
+    private Map<String,Group> groupListToMap(List<Group> allGroups)\r
+    {\r
+        Map<String,Group> groupMap = new HashMap<>();\r
+        allGroups.forEach(group -> groupMap.put(group.get_id().toString(),group));\r
+        return groupMap;\r
+    }\r
+    //get all the child group\r
+    private Set<String> getChildrenGroupsId(List<Group> enrolledGroup, Map<String,Group> allGroupsMap, User user)\r
+    {\r
+        Set<String> childrenGroups = new HashSet<>();\r
+        Set<String> managementGroupIds = getManagementGroupIds(enrolledGroup,user);\r
+        return  lookForChildren(managementGroupIds,allGroupsMap,childrenGroups);\r
+    }\r
+\r
+    private Set<String> getManagementGroupIds(List<Group> enrolledGroups,User user)\r
+    {\r
+        Set<String> parentIds = new HashSet<>();\r
+        for(Group group: enrolledGroups)\r
+        {\r
+            if(hasPermissionTo(UserPermission.Permission.MANAGEMENT,user,group)) // has Management permission\r
+            {\r
+                parentIds.add(group.get_id().toString());\r
+            }\r
+        }\r
+        return parentIds;\r
+    }\r
+    //recursive look down for childrens via breath first search\r
+    private Set<String> lookForChildren (Set<String> parentIds, Map<String,Group> allGroupsMap, Set<String> resultSet)\r
+    {\r
+        //base case = no groups to check anymore;\r
+        if (parentIds.isEmpty())\r
+            return resultSet;\r
+\r
+        Set<String> currentChildrenIds = new HashSet<>();\r
+        for(String groupId : allGroupsMap.keySet())\r
+        {\r
+            Group possibleChildGroup = allGroupsMap.get(groupId);\r
+            if(isChildOf(parentIds,possibleChildGroup)) // if parent id is the same\r
+            {\r
+                currentChildrenIds.add(groupId);\r
+                resultSet.add(groupId);\r
+            }\r
+        }\r
+        return lookForChildren(currentChildrenIds,allGroupsMap,resultSet);\r
+    }\r
+    //check if a group is a child of a list of parent group ids\r
+    private boolean isChildOf(Set<String>parentGroupIds, Group childGroup){\r
+        for(String parentId: parentGroupIds)\r
+        {\r
+            if(isChildOf(parentId,childGroup))\r
+                return true;\r
+        }\r
+        return false;\r
+    }\r
+    //check is group has parent that is specified by parentId\r
+    private boolean isChildOf(String parentId,Group childGroup) {\r
+        if(childGroup.getParentGroupId() == null)\r
+            return false;\r
+       return childGroup.getParentGroupId().toString().equals(parentId);\r
+    }\r
+\r
+    private Set<String> convertPermissions (Set<String> permissions){\r
+        Set<String> result = new HashSet<>();\r
+        for (String permission: permissions){\r
+            if(permission.equalsIgnoreCase(UserPermission.Permission.READ))\r
+                result.add(UserPermission.Permission.READ);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.WRITE))\r
+                result.add(UserPermission.Permission.WRITE);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.DELETE))\r
+                result.add(UserPermission.Permission.DELETE);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.EXECUTE))\r
+                result.add(UserPermission.Permission.EXECUTE);\r
+            else if (permission.equalsIgnoreCase(UserPermission.Permission.MANAGEMENT))\r
+                result.add(UserPermission.Permission.MANAGEMENT);\r
+        }\r
+            return result;\r
+    }\r
+}\r
diff --git a/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java b/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java
new file mode 100644 (file)
index 0000000..1883721
--- /dev/null
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.common.utility.permissions;\r
+\r
+import org.oran.otf.common.model.User;\r
+\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+public class UserPermission {\r
+    private User user;\r
+    private Map<String,Set<String>> userAccessMap;\r
+\r
+    public User getUser() {\r
+        return user;\r
+    }\r
+\r
+    public void setUser(User user) {\r
+        this.user = user;\r
+    }\r
+\r
+    public Map<String, Set<String>> getUserAccessMap() {\r
+        return userAccessMap;\r
+    }\r
+\r
+    public void setUserAccessMap(Map<String,Set<String>> userAccessMap) {\r
+        this.userAccessMap = userAccessMap;\r
+    }\r
+\r
+    public boolean  hasAccessTo(String groupId,String permission) {\r
+        if (userAccessMap.get(groupId) == null) {\r
+            return false;\r
+        }\r
+        Set<String> group = userAccessMap.get(groupId);\r
+        return group.stream().anyMatch(groupPermission->groupPermission.equalsIgnoreCase(permission));\r
+    }\r
+    public class Permission{\r
+        public static final String READ = "READ";\r
+        public static final String WRITE = "WRITE";\r
+        public static final String EXECUTE = "EXECUTE";\r
+        public static final String DELETE = "DELETE";\r
+        public static final String MANAGEMENT ="MANAGEMENT";\r
+    }\r
+}\r
diff --git a/otf-service-api/src/main/resources/application.properties b/otf-service-api/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..0a68a60
--- /dev/null
@@ -0,0 +1,50 @@
+# Tomcat\r
+server.port=8443\r
+server.port.http=8080\r
+security.require-ssl=false\r
+\r
+server.ssl.key-store-type=PKCS12\r
+server.ssl.key-store=${OTF_CERT_PATH}\r
+server.ssl.key-store-password=${OTF_CERT_PASS}\r
+#server.servlet.context-path=/otf/api\r
+#spring.jersey.application-path=/otf\r
+#springfox.documentation.swagger.v2.path=/otf/api/swagger.json\r
+\r
+# MongoDB\r
+otf.mongo.hosts=${OTF_MONGO_HOSTS}\r
+otf.mongo.username=${OTF_MONGO_USERNAME}\r
+otf.mongo.password=${OTF_MONGO_PASSWORD}\r
+otf.mongo.replicaSet=${OTF_MONGO_REPLICASET}\r
+otf.mongo.database=${OTF_MONGO_DATABASE}\r
+\r
+# Jackson\r
+spring.jackson.default-property-inclusion=always\r
+\r
+# Logging\r
+logging.level.org.springframework.web=DEBUG\r
+logging.level.org.hibernate=ERROR\r
+logging.file.max-history=5\r
+logging.file=otf/logs/serviceapi.log\r
+logging.path=otf/logs\r
+\r
+spring.resources.add-mappings=true\r
+\r
+ssl.flag =${https-only.flag:true}\r
+#springfox.documentation.auto-startup=false\r
+#springfox.documentation.swagger.v2.path=/otf/swagger.json\r
+\r
+#config\r
+aaf.enabled=true\r
+aaf.call-timeout=10000\r
+aaf.conn-timeout=6000\r
+aaf.default-realm=localhost\r
+aaf.env=PROD\r
+aaf.locate-url=https://localhost\r
+aaf.lur-class=org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm\r
+aaf.url=https://localhost\r
+basic-realm=localhost\r
+basic-warn=true\r
+cadi-latitude=38.62782\r
+cadi-longitude=-90.19458\r
+cadi-protocols=TLSv1.1,TLSv1.2\r
+cadi-noauthn=/health/v1:/demo/openapi.json
\ No newline at end of file
diff --git a/otf-service-api/src/main/resources/banner.txt b/otf-service-api/src/main/resources/banner.txt
new file mode 100644 (file)
index 0000000..544bdea
--- /dev/null
@@ -0,0 +1,8 @@
+                                                           U  ___ u   _____     _____\r
+                                                            \/"_ \/  |_ " _|   |" ___|\r
+                                                            | | | |    | |    U| |_  u\r
+                                                        .-,_| |_| |   /| |\   \|  _|/\r
+                                                         \_)-\___/   u |_|U    |_|\r
+                                                              \\     _// \\_   )(\\,-\r
+                                                             (__)   (__) (__) (__)(_/\r
+\r
diff --git a/otf-service-api/src/main/resources/truststore2018.jks b/otf-service-api/src/main/resources/truststore2018.jks
new file mode 100644 (file)
index 0000000..5d52914
Binary files /dev/null and b/otf-service-api/src/main/resources/truststore2018.jks differ
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/config/DataConfig2.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/config/DataConfig2.java
new file mode 100644 (file)
index 0000000..f6d9f66
--- /dev/null
@@ -0,0 +1,109 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.config;\r
+\r
+import com.mongodb.MongoClient;\r
+import com.mongodb.MongoClientOptions;\r
+import com.mongodb.MongoCredential;\r
+import com.mongodb.ServerAddress;\r
+import de.flapdoodle.embed.mongo.Command;\r
+import de.flapdoodle.embed.mongo.MongodExecutable;\r
+import de.flapdoodle.embed.mongo.MongodStarter;\r
+import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder;\r
+import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder;\r
+import de.flapdoodle.embed.mongo.config.IMongodConfig;\r
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;\r
+import de.flapdoodle.embed.mongo.config.Net;\r
+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;\r
+import de.flapdoodle.embed.mongo.distribution.Version;\r
+import de.flapdoodle.embed.process.config.IRuntimeConfig;\r
+import de.flapdoodle.embed.process.config.store.HttpProxyFactory;\r
+import de.flapdoodle.embed.process.runtime.Network;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Profile;\r
+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;\r
+\r
+\r
+@Configuration\r
+@EnableMongoRepositories(basePackages = "org.oran.otf.common.repository")\r
+@Profile("test")\r
+public class DataConfig2 extends AbstractMongoConfiguration {\r
+\r
+  @Value("${otf.embedded.host}")\r
+  private String host;\r
+\r
+  @Value("${otf.embedded.port}")\r
+  private int port;\r
+\r
+\r
+  @Value("${otf.embedded.database}")\r
+  private String database;\r
+\r
+  public DataConfig2(){\r
+  }\r
+\r
+  @Override\r
+  protected String getDatabaseName() {\r
+    return database;\r
+  }\r
+\r
+  /*\r
+  @Override\r
+  public MongoClient mongoClient() {\r
+    MongoCredential credential = MongoCredential.createScramSha1Credential(username, database, password.toCharArray());\r
+\r
+    MongoClientOptions options = MongoClientOptions\r
+        .builder()\r
+        .sslEnabled(false)\r
+        .requiredReplicaSetName(replicaSet)\r
+        .build();\r
+\r
+    String[] hostArray = hosts.split(",");\r
+    ArrayList<ServerAddress> hosts = new ArrayList<>();\r
+\r
+    for (String host : hostArray) {\r
+      String[] hostSplit = host.split(":");\r
+      hosts.add(new ServerAddress(hostSplit[0], Integer.parseInt(hostSplit[1])));\r
+    }\r
+\r
+    return new MongoClient(hosts, credential, options);\r
+  }\r
+\r
+  @Override\r
+  public @Bean\r
+  MongoTemplate mongoTemplate() {\r
+    return new MongoTemplate(mongoClient(), database);\r
+  }\r
+*/\r
+\r
+  @Override\r
+  public MongoClient mongoClient(){\r
+    return new MongoClient();\r
+  }\r
+\r
+  @Override\r
+  public @Bean MongoTemplate mongoTemplate(){\r
+    return new MongoTemplate(new MongoClient(host, port), "test");\r
+  }\r
+\r
+}\r
+\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/config/InMemory.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/config/InMemory.java
new file mode 100644 (file)
index 0000000..a5243a5
--- /dev/null
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.config;\r
+\r
+import de.flapdoodle.embed.mongo.Command;\r
+import de.flapdoodle.embed.mongo.MongodExecutable;\r
+import de.flapdoodle.embed.mongo.MongodStarter;\r
+import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder;\r
+import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder;\r
+import de.flapdoodle.embed.mongo.config.IMongodConfig;\r
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;\r
+import de.flapdoodle.embed.mongo.config.Net;\r
+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;\r
+import de.flapdoodle.embed.mongo.distribution.Version.Main;\r
+import de.flapdoodle.embed.process.config.IRuntimeConfig;\r
+import de.flapdoodle.embed.process.config.store.HttpProxyFactory;\r
+import de.flapdoodle.embed.process.runtime.Network;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.context.annotation.Bean;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.springframework.context.annotation.Profile;\r
+\r
+@Configuration\r
+@Profile("test")\r
+public class InMemory {\r
+  @Autowired MongodStarter mongodStarter;\r
+\r
+  @Bean\r
+  public MongodStarter mongodStarter(){\r
+    Command command = Command.MongoD;\r
+    IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()\r
+        .defaults(command)\r
+        .artifactStore(new ExtractedArtifactStoreBuilder()\r
+            .defaults(command)\r
+            .download(new DownloadConfigBuilder()\r
+                .defaultsForCommand(command)\r
+                //.downloadPath("http://fastdl.mongodb.org/win32/")\r
+                .proxyFactory(new HttpProxyFactory("localhost",8080))))\r
+             .build();\r
+\r
+    MongodStarter starter = MongodStarter.getInstance(runtimeConfig);\r
+\r
+    return MongodStarter.getInstance(runtimeConfig);\r
+  }\r
+  @Bean\r
+  public MongodExecutable mongodExecutable()throws Exception{\r
+    IMongodConfig mongodConfig = new MongodConfigBuilder().version(Main.PRODUCTION)\r
+        .net(new Net("localhost", 5555, Network.localhostIsIPv6()))\r
+        .build();\r
+    //MongodStarter starter = MongodStarter.getDefaultInstance();\r
+    return mongodStarter.prepare(mongodConfig);\r
+\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/ExecutionServiceRouteIT.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/ExecutionServiceRouteIT.java
new file mode 100644 (file)
index 0000000..85d7016
--- /dev/null
@@ -0,0 +1,79 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.integration.services;\r
+\r
+import org.oran.otf.api.Application;\r
+import org.oran.otf.api.tests.shared.MemoryDatabase;\r
+import io.restassured.RestAssured;\r
+import org.junit.After;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Ignore;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.springframework.boot.test.context.SpringBootTest;\r
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\r
+import org.springframework.boot.web.server.LocalServerPort;\r
+import org.springframework.test.context.ActiveProfiles;\r
+import org.springframework.test.context.TestPropertySource;\r
+import org.springframework.test.context.junit4.SpringRunner;\r
+\r
+@RunWith(SpringRunner.class)\r
+@SpringBootTest(\r
+    webEnvironment = WebEnvironment.RANDOM_PORT,\r
+    classes = {Application.class}\r
+)\r
+@TestPropertySource("classpath:application-test.properties")\r
+@ActiveProfiles("test")\r
+public class ExecutionServiceRouteIT {\r
+  @LocalServerPort\r
+  private int port;\r
+\r
+  @BeforeClass\r
+  public static void setup() throws Exception{\r
+    MemoryDatabase.setup();\r
+  }\r
+  @AfterClass\r
+  public static void cleanup(){\r
+    MemoryDatabase.cleanup();\r
+  }\r
+\r
+  @Before\r
+  public void setupRestAssured() throws Exception{\r
+    RestAssured.port = port;\r
+    RestAssured.urlEncodingEnabled = false;\r
+    RestAssured.baseURI = "https://localhost";\r
+    RestAssured.basePath="/otf/api/testExecution/v1";\r
+    RestAssured.useRelaxedHTTPSValidation();\r
+  }\r
+\r
+  @Ignore\r
+  @Test\r
+  public void testExecutionServiceRouteRespondsWith200(){}\r
+  @Test\r
+  public void testExecutionServiceRouteStatusRespondsWithOnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/status/executionId/abced").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testExecutionServiceRouteExecutionIdRespondsWithOnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/executionId/abced").then().assertThat().statusCode(401);\r
+  }\r
+\r
+\r
+}\r
+\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/HealthRouteIT.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/HealthRouteIT.java
new file mode 100644 (file)
index 0000000..a04169e
--- /dev/null
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.integration.services;\r
+\r
+import static org.hamcrest.CoreMatchers.equalTo;\r
+\r
+import org.oran.otf.api.Application;\r
+import org.oran.otf.api.tests.shared.MemoryDatabase;\r
+import io.restassured.RestAssured;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.springframework.boot.test.context.SpringBootTest;\r
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\r
+import org.springframework.boot.web.server.LocalServerPort;\r
+import org.springframework.test.context.ActiveProfiles;\r
+import org.springframework.test.context.TestPropertySource;\r
+import org.springframework.test.context.junit4.SpringRunner;\r
+\r
+@RunWith(SpringRunner.class)\r
+@SpringBootTest(\r
+    webEnvironment = WebEnvironment.RANDOM_PORT,\r
+    classes = {\r
+        Application.class\r
+    })\r
+@TestPropertySource("classpath:application-test.properties")\r
+@ActiveProfiles("test")\r
+public class HealthRouteIT {\r
+  @LocalServerPort\r
+  private int port;\r
+\r
+\r
+  @BeforeClass\r
+  public static void setup()throws Exception{\r
+    MemoryDatabase.setup();\r
+  }\r
+  @AfterClass\r
+  public static void cleanup(){\r
+    MemoryDatabase.cleanup();\r
+  }\r
+  @Before\r
+  public void setupRestAssured(){\r
+    RestAssured.port = port;\r
+    RestAssured.baseURI="https://localhost";\r
+    RestAssured.basePath="/otf/api";\r
+    RestAssured.urlEncodingEnabled =false;\r
+    RestAssured.useRelaxedHTTPSValidation();\r
+\r
+  }\r
+  @Test\r
+  public void testHealthRouteRespondsWith200(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/health/v1").then().assertThat().statusCode(200);\r
+  }\r
+  @Test\r
+  public void testHealthRouteRespondsWithUp(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/health/v1").then().assertThat().body("message", equalTo("UP"));\r
+  }\r
+  @Test\r
+  public void testHealthRouteRespondsWithJson(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/health/v1").then().contentType("application/json");\r
+  }\r
+\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/InstanceServiceRouteIT.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/InstanceServiceRouteIT.java
new file mode 100644 (file)
index 0000000..a16a23c
--- /dev/null
@@ -0,0 +1,170 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.integration.services;\r
+\r
+import static org.hamcrest.CoreMatchers.containsString;\r
+import static org.hamcrest.CoreMatchers.equalTo;\r
+\r
+import org.oran.otf.api.Application;\r
+import org.oran.otf.api.tests.shared.MemoryDatabase;\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.oran.otf.common.model.User;\r
+import io.restassured.RestAssured;\r
+import org.eclipse.jetty.http.QuotedQualityCSV;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.beans.factory.annotation.Value;\r
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;\r
+import org.springframework.boot.test.context.SpringBootTest;\r
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\r
+import org.springframework.boot.web.server.LocalServerPort;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.test.context.ActiveProfiles;\r
+import org.springframework.test.context.TestPropertySource;\r
+import org.springframework.test.context.junit4.SpringRunner;\r
+\r
+@RunWith(SpringRunner.class)\r
+@SpringBootTest(\r
+    webEnvironment = WebEnvironment.RANDOM_PORT,\r
+    classes = {Application.class}\r
+)\r
+@TestPropertySource("classpath:application-test.properties")\r
+@ActiveProfiles("test")\r
+public class InstanceServiceRouteIT {\r
+  @LocalServerPort\r
+  private int port;\r
+  @Value("${otf.mechid}")\r
+  private String username;\r
+  @Value("${otf.mechpass}")\r
+  private String password;\r
+  private static User mechUser;\r
+\r
+  @Autowired\r
+  private MongoTemplate mongoTemplate;\r
+\r
+  @BeforeClass\r
+  public static void setup() throws Exception{\r
+    MemoryDatabase.createAllTables();\r
+    MemoryDatabase.createAllAdmin();\r
+    //mechUser = MemoryDatabase.createMechUser();\r
+  }\r
+  @AfterClass\r
+  public static void cleanup(){\r
+    MemoryDatabase.cleanup();\r
+  }\r
+  @Before\r
+  public void setupRestAssured() {\r
+    RestAssured.port = port;\r
+    RestAssured.urlEncodingEnabled = false;\r
+    RestAssured.baseURI = "https://localhost";\r
+    RestAssured.basePath="/otf/api/testInstance";\r
+    RestAssured.useRelaxedHTTPSValidation();\r
+  }\r
+  //NoAuth Tests\r
+\r
+  @Test\r
+  public void testFindByIdRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/v1/id/abced").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testFindByProcessKeyRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/v1/processDefinitionKey/abced").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testFindByProcessKeyAndVersionRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/v1/processDefinitionKey/abced/version/1").then().assertThat().statusCode(401);\r
+  }\r
+\r
+\r
+  @Test\r
+  public void testExecuteRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/execute/v1/id/abced/").then().assertThat().statusCode(401);\r
+  }\r
+\r
+\r
+  @Test\r
+  public void testCreateByTestDefinitionIdRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/testDefinitionId/abced/").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testCreateByTestDefinitionIdAndVersionRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/testDefinitionId/abced/version/2").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testCreateByProcessDefinitionKeyRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/processDefinitionKey/abced").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testCreateByProcessDefinitionKeyAndVersionRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/processDefinitionKey/abced/version/2").then().assertThat().statusCode(401);\r
+  }\r
+\r
+  //With Auth and Wrong id\r
+  @Test\r
+  public void testFindByIdRespondsWith400OnWrongId(){\r
+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "application/json").get("/v1/id/abced").then().assertThat().statusCode(400);\r
+  }\r
+  @Test\r
+  public void testFindByIdRespondsWithMessageOnWrongId(){\r
+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "application/json").get("/v1/id/abcde").then().assertThat().body("message", containsString("is not a valid ObjectId (BSON)"));\r
+  }\r
+  @Test\r
+  public void testFindByProcessDefinitionRespondsWith400OnWrongProcessDefinition(){\r
+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("testDef1")), TestDefinition.class);\r
+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "*/*").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()).then().assertThat().statusCode(400);\r
+  }\r
+  @Test\r
+  public void testFindByProcessDefinitionRespondsWithMessageOnWrongProcessDefinition(){\r
+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("testDef1")), TestDefinition.class);\r
+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "application/json").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()).then().assertThat().body("message", containsString("No test instances found"));\r
+  }\r
+\r
+  //Successful Get Methods\r
+\r
+  @Test\r
+  public void testFindByIdRespondsWith200OnSuccess(){\r
+    TestInstance testInstance = mongoTemplate.findOne(new Query(Criteria.where("testInstanceName").is("MechTestInstance")), TestInstance.class);\r
+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "*/*").get("/v1/id/"+testInstance.get_id()).then().assertThat().statusCode(200);\r
+  }\r
+\r
+  @Test\r
+  public void testFindByProcessDefinitionKeyRespondsWith200OnSuccess(){\r
+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("MechTestDefinition")), TestDefinition.class);\r
+    RestAssured.given().auth().basic(username, password).log().all().header("Accept", "*/*").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()).then().assertThat().statusCode(200);\r
+  }\r
+\r
+  @Test\r
+  public void testFindByProcessDefinitionKeyAndVersionRespondsWith200OnSuccess(){\r
+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("MechTestDefinition")), TestDefinition.class);\r
+    RestAssured.given().auth().basic(username, password).log().all().header("Accept", "*/*").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()+"/version/"+1).then().assertThat().statusCode(200);\r
+  }\r
+\r
+  @Test\r
+  public void testCreateByTestDefinitionIdRespondsWith201OnSuccess(){\r
+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("MechTestDefinition")), TestDefinition.class);\r
+    System.out.println(testDefinition.getBpmnInstances());\r
+    RestAssured.given().contentType("application/json\r\n").auth().basic(username, password).log().all().header("Accept", "*/*").post("/create/v1/testDefinitionId/"+testDefinition.get_id()+"/version/"+1).then().assertThat().statusCode(404);\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/OtfOpenRouteIT.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/OtfOpenRouteIT.java
new file mode 100644 (file)
index 0000000..132464a
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.integration.services;\r
+\r
+import org.oran.otf.api.Application;\r
+import org.oran.otf.api.tests.shared.MemoryDatabase;\r
+import io.restassured.RestAssured;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Ignore;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.springframework.boot.test.context.SpringBootTest;\r
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\r
+import org.springframework.boot.web.server.LocalServerPort;\r
+import org.springframework.test.context.ActiveProfiles;\r
+import org.springframework.test.context.TestPropertySource;\r
+import org.springframework.test.context.junit4.SpringRunner;\r
+\r
+@RunWith(SpringRunner.class)\r
+@SpringBootTest(\r
+    webEnvironment = WebEnvironment.RANDOM_PORT,\r
+    classes = {Application.class}\r
+)\r
+@TestPropertySource("classpath:application-test.properties")\r
+@ActiveProfiles("test")\r
+public class OtfOpenRouteIT {\r
+  @LocalServerPort\r
+  private int port;\r
+\r
+  @BeforeClass\r
+  public static void setup() throws Exception{\r
+    MemoryDatabase.setup();\r
+  }\r
+  @AfterClass\r
+  public static void cleanup(){\r
+    MemoryDatabase.cleanup();\r
+  }\r
+  @Before\r
+  public void setupRestAssured(){\r
+    RestAssured.port =port;\r
+    RestAssured.urlEncodingEnabled = false;\r
+    RestAssured.baseURI="https://localhost";\r
+    RestAssured.basePath="/otf/api";\r
+    RestAssured.useRelaxedHTTPSValidation();\r
+  }\r
+  @Ignore("Ignoring test because it fails since it tries to request to specific port, uncomment to view error")\r
+  @Test\r
+  public void testOtfOpenRouteRespondsWith200(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").get("/demo/openapi.json").then().statusCode(200);\r
+  }\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/Permissions/PermissionServiceIT.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/Permissions/PermissionServiceIT.java
new file mode 100644 (file)
index 0000000..6186d3a
--- /dev/null
@@ -0,0 +1,331 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.integration.services.Permissions;\r
+\r
+import org.oran.otf.api.Application;\r
+import org.oran.otf.api.tests.shared.MemoryDatabase;\r
+import org.oran.otf.common.model.Group;\r
+import org.oran.otf.common.model.GroupMember;\r
+import org.oran.otf.common.model.User;\r
+import org.oran.otf.common.repository.GroupRepository;\r
+import org.oran.otf.common.utility.permissions.PermissionChecker;\r
+import org.oran.otf.common.utility.permissions.PermissionUtil;\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import org.bson.types.ObjectId;\r
+import org.junit.*;\r
+import org.junit.runner.RunWith;\r
+import org.mockito.Mockito;\r
+import org.springframework.beans.factory.annotation.Autowired;\r
+import org.springframework.boot.test.context.SpringBootTest;\r
+import org.springframework.test.context.ActiveProfiles;\r
+import org.springframework.test.context.TestPropertySource;\r
+import org.springframework.test.context.junit4.SpringRunner;\r
+\r
+import java.util.*;\r
+\r
+@RunWith(SpringRunner.class)\r
+@SpringBootTest(\r
+        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,\r
+        classes = {\r
+                Application.class,\r
+        })\r
+@TestPropertySource("classpath:application-test.properties")\r
+@ActiveProfiles("test")\r
+public class PermissionServiceIT {\r
+    @Autowired\r
+    private GroupRepository groupRepository;\r
+    private List<Group> groups;\r
+    private Group parentGroup;\r
+    private Group firstChildGroup;\r
+    private Group childOfChildGroup;\r
+\r
+    @BeforeClass\r
+    public static void setUp() throws Exception{\r
+        MemoryDatabase.setup();\r
+        MemoryDatabase.createGroupsForPermission();\r
+    }\r
+    @Before\r
+    public void setUpGroups()\r
+    {\r
+        groups = groupRepository.findAll();\r
+        parentGroup = groupRepository.findFirstByGroupName("parent group");\r
+        firstChildGroup = groupRepository.findFirstByGroupName("first child group");\r
+        childOfChildGroup = groupRepository.findFirstByGroupName("child of child group");\r
+    }\r
+    @AfterClass\r
+    public static void cleanup(){\r
+        MemoryDatabase.cleanup();\r
+    }\r
+    /*\r
+     if this test failed there was a error during set up so ignore the failures produced by other tests til this pass\r
+    */\r
+    @Test\r
+    public void setUpTest(){\r
+        List<Group> groups = groupRepository.findAll();\r
+        parentGroup = groupRepository.findFirstByGroupName("parent group");\r
+        firstChildGroup = groupRepository.findFirstByGroupName("first child group");\r
+        childOfChildGroup = groupRepository.findFirstByGroupName("child of child group");\r
+        Assert.assertNotNull(groups);\r
+        Assert.assertFalse(groups.isEmpty());\r
+\r
+        Assert.assertNotNull(parentGroup.getMembers());\r
+        Assert.assertFalse(parentGroup.getMembers().isEmpty());\r
+        Assert.assertNotNull(parentGroup.getRoles());\r
+        Assert.assertFalse(parentGroup.getRoles().isEmpty());\r
+\r
+        Assert.assertNotNull(firstChildGroup.getMembers());\r
+        Assert.assertFalse(firstChildGroup.getMembers().isEmpty());\r
+        Assert.assertNotNull(firstChildGroup.getRoles());\r
+        Assert.assertFalse(firstChildGroup.getRoles().isEmpty());\r
+\r
+        Assert.assertNotNull(childOfChildGroup.getMembers());\r
+        Assert.assertFalse(childOfChildGroup.getMembers().isEmpty());\r
+        Assert.assertNotNull(childOfChildGroup.getRoles());\r
+        Assert.assertFalse(childOfChildGroup.getRoles().isEmpty());\r
+        // all groups are set up with 1 member in memory db\r
+        Assert.assertEquals(1,parentGroup.getMembers().size());\r
+        Assert.assertEquals(1,firstChildGroup.getMembers().size());\r
+        Assert.assertEquals(1,childOfChildGroup.getMembers().size());\r
+    }\r
+    @Test\r
+    public void findUserRoles(){\r
+        GroupMember parentMember = parentGroup.getMembers().get(0);\r
+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);\r
+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);\r
+\r
+        User parentUserMock = Mockito.mock(User.class);\r
+        User firstChildUserMock = Mockito.mock(User.class);\r
+        User childOfChildUserMock = Mockito.mock(User.class);\r
+\r
+        Mockito.when(parentUserMock.get_id()).thenReturn(parentMember.getUserId());\r
+        Mockito.when(firstChildUserMock.get_id()).thenReturn(firstChildMember.getUserId());\r
+        Mockito.when(childOfChildUserMock.get_id()).thenReturn(childOfChildMember.getUserId());\r
+\r
+        Set<String> parentMemberRoles = PermissionUtil.findUserRoles(parentUserMock, parentGroup);\r
+        Set<String> firstChildRoles = PermissionUtil.findUserRoles(firstChildUserMock, firstChildGroup);\r
+        Set<String> childOfChildRoles = PermissionUtil.findUserRoles(childOfChildUserMock, childOfChildGroup);\r
+\r
+        // all group members should only have 1 role (admin) set up except first child\r
+        Assert.assertEquals(1,parentMemberRoles.size());\r
+        Assert.assertTrue(parentMemberRoles.contains("admin"));\r
+        Assert.assertEquals(2,firstChildRoles.size());\r
+        Assert.assertTrue(firstChildRoles.contains("admin"));\r
+        Assert.assertTrue(firstChildRoles.contains("dev"));\r
+        Assert.assertEquals(1,childOfChildRoles.size());\r
+        Assert.assertTrue(childOfChildRoles.contains("executor"));\r
+\r
+        Assert.assertFalse(parentMemberRoles.contains("executor"));\r
+        Assert.assertFalse(firstChildRoles.contains("executor"));\r
+        Assert.assertFalse("should not have admin roles in child of child", childOfChildRoles.contains("admin"));\r
+    }\r
+    @Test\r
+    public void getRolePermissionsTest()\r
+    {\r
+        ObjectId firstChildId =firstChildGroup.getMembers().get(0).getUserId();\r
+        User firstChildUserMock = Mockito.mock(User.class);\r
+        Mockito.when(firstChildUserMock.get_id()).thenReturn(firstChildId);\r
+        Set<String> roles = PermissionUtil.findUserRoles(firstChildUserMock,firstChildGroup); //dev and admin roles only\r
+\r
+        Assert.assertEquals(2,roles.size());\r
+        for(String role : roles){\r
+            Set<String> permissions = PermissionUtil.getRolePermissions(role,parentGroup);\r
+            Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("READ"));\r
+            Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("WRITE"));\r
+            Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("DELETE"));\r
+            Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("EXECUTE"));\r
+        }\r
+    }\r
+    @Test\r
+    public void getUserGroupPermissionTest(){\r
+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);\r
+        User firstChildUser = Mockito.mock(User.class);\r
+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());\r
+        Set<String> permissions = PermissionUtil.getUserGroupPermissions(firstChildUser,firstChildGroup); // should include everything except execute and delete\r
+\r
+        Assert.assertEquals(3,permissions.size());\r
+        Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("READ"));\r
+        Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("WRITE"));\r
+        Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("DELETE"));\r
+        Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("EXECUTE"));\r
+        Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("MANAGEMENT"));\r
+    }\r
+\r
+    @Test\r
+    public void hasPermissionToTest(){\r
+        GroupMember parentMember = parentGroup.getMembers().get(0);\r
+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);\r
+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);\r
+\r
+        User parentGroupUser = Mockito.mock(User.class);\r
+        User firstChildUser = Mockito.mock(User.class);\r
+        User childOfChildUser =Mockito.mock(User.class);\r
+        Mockito.when(parentGroupUser.get_id()).thenReturn(parentMember.getUserId());\r
+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());\r
+        Mockito.when(childOfChildUser.get_id()).thenReturn(childOfChildMember.getUserId());\r
+\r
+        String read = "read";\r
+        String write= "write";\r
+        String manage = "management";\r
+        String delete = "delete";\r
+        String execute= "execute";\r
+\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(read,parentGroupUser,parentGroup));\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(write,parentGroupUser,parentGroup));\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(manage,parentGroupUser,parentGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(delete,parentGroupUser,parentGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(execute,parentGroupUser,parentGroup));\r
+\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(read,firstChildUser,firstChildGroup));\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(write,firstChildUser,firstChildGroup));\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(manage,firstChildUser,firstChildGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(delete,firstChildUser,firstChildGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(execute,firstChildUser,firstChildGroup));\r
+\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(read,childOfChildUser,childOfChildGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(write,childOfChildUser,childOfChildGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(manage,childOfChildUser,childOfChildGroup));\r
+        Assert.assertFalse(PermissionUtil.hasPermissionTo(delete,childOfChildUser,childOfChildGroup));\r
+        Assert.assertTrue(PermissionUtil.hasPermissionTo(execute,childOfChildUser,childOfChildGroup));\r
+    }\r
+    @Test\r
+    public void buildUserPermissionTest()\r
+    {\r
+        /*\r
+           should be the following format\r
+           parent members:\r
+           parentGroup = {read,write,management}\r
+           first Child group = {read}\r
+           child of child group = {read}\r
+\r
+           first child group:\r
+           parentGroup = {read}\r
+           first Child group = {read,write,management}\r
+           child of child group = {read}\r
+\r
+           child of child:\r
+           parentGroup = {read}\r
+           first Child group = {read}\r
+           child of child group = {execute}\r
+         */\r
+\r
+        GroupMember parentMember = parentGroup.getMembers().get(0);\r
+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);\r
+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);\r
+\r
+        User parentGroupUser = Mockito.mock(User.class);\r
+        User firstChildUser = Mockito.mock(User.class);\r
+        User childOfChildUser =Mockito.mock(User.class);\r
+        Mockito.when(parentGroupUser.get_id()).thenReturn(parentMember.getUserId());\r
+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());\r
+        Mockito.when(childOfChildUser.get_id()).thenReturn(childOfChildMember.getUserId());\r
+\r
+        String read = "READ";\r
+        String write= "WRITE";\r
+        String manage = "MANAGEMENT";\r
+        String delete = "DELETE";\r
+        String execute= "EXECUTE";\r
+\r
+        UserPermission parentUserPermissions = new PermissionUtil().buildUserPermission(parentGroupUser,groupRepository);\r
+        UserPermission firstChildUserPermissions = new PermissionUtil().buildUserPermission(firstChildUser,groupRepository);\r
+        UserPermission childOfChildUserPermissions = new PermissionUtil().buildUserPermission(childOfChildUser,groupRepository);\r
+        Map<String,Set<String>> parentAccessControl = parentUserPermissions.getUserAccessMap();\r
+        Map<String,Set<String>> firstChildAccessControl = firstChildUserPermissions.getUserAccessMap();\r
+        Map<String,Set<String>> childOfChildAccessControl = childOfChildUserPermissions.getUserAccessMap();\r
+\r
+        //test for parent access control\r
+        Assert.assertTrue(parentAccessControl.get(parentGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(parentAccessControl.get(parentGroup.get_id().toString()).contains(write));\r
+        Assert.assertTrue(parentAccessControl.get(parentGroup.get_id().toString()).contains(manage));\r
+        //test all access is passed to firstChildGroup\r
+        Assert.assertTrue(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(write));\r
+        Assert.assertTrue(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(manage));\r
+        //test all access is passed to child of child group\r
+        Assert.assertTrue(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(write));\r
+        Assert.assertTrue(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(manage));\r
+        // make sure parent user dont have other permissions in first child group\r
+        Assert.assertFalse(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(execute));\r
+        //test that parent dont have other permissions in child of child group\r
+        Assert.assertFalse(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(execute));\r
+\r
+        //test for first child access control\r
+        Assert.assertTrue(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(firstChildAccessControl.get(firstChildGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(firstChildAccessControl.get(firstChildGroup.get_id().toString()).contains(write));\r
+        Assert.assertTrue(firstChildAccessControl.get(firstChildGroup.get_id().toString()).contains(manage));\r
+        // test that first child group get passed to child of child\r
+        Assert.assertTrue(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(write));\r
+        Assert.assertTrue(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(manage));\r
+        // make sure firstchild user dont have other permissions\r
+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(write));\r
+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(manage));\r
+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(execute));\r
+        // test to confirm no extra permission is passed to child of child\r
+        Assert.assertFalse(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(execute));\r
+\r
+        //test for child of child access control\r
+        Assert.assertTrue(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(read));\r
+        Assert.assertTrue(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(execute));\r
+        // make sure child of child user dont have other permissions\r
+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(write));\r
+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(manage));\r
+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(execute));\r
+\r
+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(write));\r
+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(manage));\r
+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(execute));\r
+\r
+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(write));\r
+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(manage));\r
+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(delete));\r
+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(read));\r
+    }\r
+    @Test\r
+    public void basicTest(){\r
+        GroupMember parentMember = parentGroup.getMembers().get(0);\r
+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);\r
+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);\r
+\r
+        User parentGroupUser = Mockito.mock(User.class);\r
+        User firstChildUser = Mockito.mock(User.class);\r
+        User childOfChildUser =Mockito.mock(User.class);\r
+        Mockito.when(parentGroupUser.get_id()).thenReturn(parentMember.getUserId());\r
+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());\r
+        Mockito.when(childOfChildUser.get_id()).thenReturn(childOfChildMember.getUserId());\r
+\r
+        Assert.assertTrue(PermissionChecker.hasPermissionTo(childOfChildUser,firstChildGroup,UserPermission.Permission.READ,groupRepository));\r
+        Assert.assertTrue(PermissionChecker.hasPermissionTo(childOfChildUser,parentGroup,UserPermission.Permission.READ,groupRepository));\r
+        Assert.assertFalse(PermissionChecker.hasPermissionTo(childOfChildUser,childOfChildGroup,UserPermission.Permission.READ,groupRepository));\r
+\r
+        Assert.assertFalse(PermissionChecker.hasPermissionTo(childOfChildUser,firstChildGroup,UserPermission.Permission.EXECUTE,groupRepository));\r
+        Assert.assertTrue(PermissionChecker.hasPermissionTo(firstChildUser,firstChildGroup,UserPermission.Permission.WRITE,groupRepository));\r
+        Assert.assertFalse(PermissionChecker.hasPermissionTo(firstChildUser,firstChildGroup,UserPermission.Permission.EXECUTE,groupRepository));\r
+\r
+        Assert.assertFalse(PermissionChecker.hasPermissionTo(parentGroupUser,parentGroup,UserPermission.Permission.DELETE,groupRepository));\r
+        Assert.assertFalse(PermissionChecker.hasPermissionTo(parentGroupUser,parentGroup,UserPermission.Permission.EXECUTE,groupRepository));\r
+        Assert.assertFalse(PermissionChecker.hasPermissionTo(parentGroupUser,firstChildGroup,UserPermission.Permission.EXECUTE,groupRepository));\r
+    }\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/StrategyServiceRouteIT.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/StrategyServiceRouteIT.java
new file mode 100644 (file)
index 0000000..7cd2b43
--- /dev/null
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.integration.services;\r
+\r
+import org.oran.otf.api.Application;\r
+import org.oran.otf.api.tests.shared.MemoryDatabase;\r
+import io.restassured.RestAssured;\r
+import org.junit.AfterClass;\r
+import org.junit.Before;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.springframework.boot.test.context.SpringBootTest;\r
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\r
+import org.springframework.boot.web.server.LocalServerPort;\r
+import org.springframework.test.context.ActiveProfiles;\r
+import org.springframework.test.context.TestPropertySource;\r
+import org.springframework.test.context.junit4.SpringRunner;\r
+\r
+@RunWith(SpringRunner.class)\r
+@SpringBootTest(\r
+    webEnvironment = WebEnvironment.RANDOM_PORT,\r
+    classes = {Application.class}\r
+)\r
+@TestPropertySource("classpath:application-test.properties")\r
+@ActiveProfiles("test")\r
+public class StrategyServiceRouteIT {\r
+  @LocalServerPort\r
+  private int port;\r
+  @BeforeClass\r
+  public static void setup() throws Exception{\r
+    MemoryDatabase.setup();\r
+  }\r
+  @AfterClass\r
+  public static void cleanup(){\r
+    MemoryDatabase.cleanup();\r
+  }\r
+  @Before\r
+  public void setupRestAssured(){\r
+    RestAssured.port = port;\r
+    RestAssured.baseURI="https://localhost";\r
+    RestAssured.basePath="/otf/api/testStrategy";\r
+    RestAssured.urlEncodingEnabled=false;\r
+    RestAssured.useRelaxedHTTPSValidation();\r
+  }\r
+\r
+  @Test\r
+  public void testStrategyServiceRouteDeployRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").post("/deploy/v1").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testStrategyServiceRouteDeleteByTestDefinitionIdRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").delete("/delete/v1/testDefinitionId/56565656").then().assertThat().statusCode(401);\r
+  }\r
+  @Test\r
+  public void testStrategyServiceRouteDeleteByDeploymentIdRespondsWith401OnNoAuth(){\r
+    RestAssured.given().log().all().header("Accept", "application/json").delete("/delete/v1/deploymentId/54545454").then().assertThat().statusCode(401);\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java
new file mode 100644 (file)
index 0000000..2c17abb
--- /dev/null
@@ -0,0 +1,386 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.shared;\r
+\r
+import org.oran.otf.common.model.*;\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.oran.otf.common.model.local.UserGroup;\r
+import com.mongodb.BasicDBObjectBuilder;\r
+import com.mongodb.DBObject;\r
+import com.mongodb.MongoClient;\r
+import de.flapdoodle.embed.mongo.Command;\r
+import de.flapdoodle.embed.mongo.MongodExecutable;\r
+import de.flapdoodle.embed.mongo.MongodProcess;\r
+import de.flapdoodle.embed.mongo.MongodStarter;\r
+import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder;\r
+import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder;\r
+import de.flapdoodle.embed.mongo.config.IMongodConfig;\r
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;\r
+import de.flapdoodle.embed.mongo.config.Net;\r
+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;\r
+import de.flapdoodle.embed.mongo.distribution.Version;\r
+import de.flapdoodle.embed.mongo.distribution.Version.Main;\r
+import de.flapdoodle.embed.process.config.IRuntimeConfig;\r
+import de.flapdoodle.embed.process.config.store.HttpProxyFactory;\r
+import de.flapdoodle.embed.process.runtime.Network;\r
+\r
+import java.sql.Timestamp;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Date;\r
+import java.util.List;\r
+import java.util.Random;\r
+import javassist.util.proxy.ProxyFactory;\r
+import org.apache.commons.lang3.time.DateUtils;\r
+import org.apache.http.HttpResponse;\r
+import org.apache.http.client.methods.HttpGet;\r
+import org.oran.otf.common.model.*;\r
+import org.springframework.context.annotation.Configuration;\r
+import org.bson.types.ObjectId;\r
+import org.springframework.data.mongodb.core.MongoTemplate;\r
+import org.springframework.data.mongodb.core.query.Criteria;\r
+import org.springframework.data.mongodb.core.query.Query;\r
+import org.springframework.test.context.ActiveProfiles;\r
+\r
+\r
+@ActiveProfiles("test")\r
+public abstract class MemoryDatabase {\r
+  protected static MongodExecutable mongodExecutable;\r
+  protected static MongoTemplate mongoTemplate;\r
+\r
+  //TODO use mongod process to be response from mongodExecutable.start(), on pulbic calls check if null if so call setup else dont\r
+  protected static MongodProcess mongod = null;\r
+\r
+  protected static Query userQuery = new Query(Criteria.where("firstName").is("Mech"));\r
+  //protected static Query mechUserQuery = new Query(Criteria.where("firstName").is("Mech"));\r
+  protected static Query testInstanceQuery = new Query(Criteria.where("testInstanceName").is("MechTestInstance"));\r
+  protected static Query groupQuery = new Query(Criteria.where("groupName").is("MechGroup"));\r
+  protected static Query testDefQuery = new Query(Criteria.where("testName").is("MechTestDefinition"));\r
+\r
+  //values should match with DataConfig2\r
+  protected static int port=5555;\r
+  protected static String host="localhost";\r
+\r
+\r
+  public static void setup()throws Exception{\r
+    Command command = Command.MongoD;\r
+    IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()\r
+        .defaults(command)\r
+        .artifactStore(new ExtractedArtifactStoreBuilder()\r
+            .defaults(command)\r
+            .download(new DownloadConfigBuilder()\r
+                .defaultsForCommand(command)\r
+                .proxyFactory(new HttpProxyFactory("localhost",8080))))\r
+             .build();\r
+\r
+    //String host = "localhost";\r
+    //int port = 5555;\r
+\r
+    IMongodConfig mongodConfig = new MongodConfigBuilder().version(Main.PRODUCTION)\r
+        .net(new Net(host, port, Network.localhostIsIPv6()))\r
+        .build();\r
+    //MongodStarter starter = MongodStarter.getDefaultInstance();\r
+    MongodStarter starter = MongodStarter.getInstance(runtimeConfig);\r
+    mongodExecutable = starter.prepare(mongodConfig);\r
+    mongodExecutable.start();\r
+    mongoTemplate = new MongoTemplate(new MongoClient(host, port), "test");\r
+\r
+    DBObject objectToSave = BasicDBObjectBuilder.start()\r
+        .add("name", "john")\r
+        .get();\r
+    mongoTemplate.save(objectToSave, "collection");\r
+\r
+\r
+  }\r
+  /*\r
+  public static User createMechUser(){\r
+\r
+    User user = mongoTemplate.findOne(mechUserQuery, User.class);\r
+    if(user == null) {\r
+      user = new User();\r
+      user.setFirstName("Mech");\r
+      user.setLastName("Id");\r
+      user.setEmail("email@localhost");\r
+      mongoTemplate.save(user, "users");\r
+      user = mongoTemplate.findOne(mechUserQuery, User.class);\r
+    }\r
+    return user;\r
+  }\r
+\r
+   */\r
+  //TODO: make admin user be the mechid, this is because of AAF test will fail if random user is used\r
+  private static User createMechUserIfNotExists(){\r
+    User user = mongoTemplate.findOne(userQuery, User.class);\r
+    if(user == null) {\r
+      user = new User();\r
+      user.setFirstName("Mech");\r
+      user.setLastName("Id");\r
+      user.setEmail("email@localhost");\r
+      mongoTemplate.save(user, "users");\r
+      user = mongoTemplate.findOne(userQuery, User.class);\r
+    }\r
+    return user;\r
+\r
+  }\r
+  private static Group createMechGroupIfNotExists(){\r
+    User user = MemoryDatabase.createMechUserIfNotExists();\r
+    Group group = mongoTemplate.findOne(groupQuery, Group.class);\r
+    if(group == null) {\r
+      String groupName = "MechGroup";\r
+      group = new Group();\r
+      group.setOwnerId(user.get_id());\r
+      group.setGroupName(groupName);\r
+      group.setGroupDescription(groupName + " description");\r
+      mongoTemplate.save(group, "groups");\r
+      group = mongoTemplate.findOne(groupQuery, Group.class);\r
+    }\r
+    return group;\r
+  }\r
+  private static TestDefinition createMechTestDefinitionIfNotExists(){\r
+    TestDefinition testDefinition = mongoTemplate.findOne(testDefQuery, TestDefinition.class);\r
+    if(testDefinition == null){\r
+\r
+      BpmnInstance bpmnInstance = new BpmnInstance();\r
+      bpmnInstance.setDeployed(true);\r
+      bpmnInstance.setVersion(1);\r
+      List list = new ArrayList(Arrays.asList(bpmnInstance));\r
+\r
+      testDefinition = new TestDefinition();\r
+      testDefinition.setDisabled(false);\r
+      testDefinition.setBpmnInstances(list);\r
+      testDefinition.setTestName("MechTestDefinition");\r
+      testDefinition.setTestDescription("MechTestDefinition description");\r
+      testDefinition.setProcessDefinitionKey("MechTestDefinitionKey");\r
+      testDefinition.setCreatedBy(createMechUserIfNotExists().get_id());\r
+      testDefinition.setGroupId(createMechGroupIfNotExists().get_id());\r
+      testDefinition.setCreatedAt(new Timestamp(new Date().getTime()));\r
+      testDefinition.setUpdatedAt(new Timestamp(new Date().getTime()));\r
+      mongoTemplate.save(testDefinition, "testDefinitions");\r
+      testDefinition = mongoTemplate.findOne(testDefQuery, TestDefinition.class);\r
+    }\r
+    return testDefinition;\r
+\r
+  }\r
+\r
+\r
+  private static TestInstance createMechTestInstanceIfNotExists(){\r
+    TestInstance testInstance = mongoTemplate.findOne(testInstanceQuery, TestInstance.class);\r
+    User user = createMechUserIfNotExists();\r
+    UserGroup userGroup = new UserGroup();\r
+    if(testInstance == null){\r
+      testInstance = new TestInstance();\r
+      testInstance.setTestInstanceName("MechTestInstance");\r
+      testInstance.setTestInstanceDescription("MechTestInstance description");\r
+      testInstance.setCreatedBy(user.get_id());\r
+      testInstance.setGroupId(createMechGroupIfNotExists().get_id());\r
+      testInstance.setTestDefinitionId(createMechTestDefinitionIfNotExists().get_id());\r
+      testInstance.setMaxExecutionTimeInMillis(new Random().nextInt(5000));\r
+      testInstance.setUseLatestTestDefinition(true);\r
+      mongoTemplate.save(testInstance, "testInstances");\r
+      testInstance = mongoTemplate.findOne(testInstanceQuery, TestInstance.class);\r
+    }\r
+    userGroup.setGroupId(testInstance.getGroupId());\r
+    userGroup.setPermissions(Arrays.asList("Admin"));\r
+    user.setGroups(Arrays.asList(userGroup));\r
+    mongoTemplate.save(user, "users");\r
+    return testInstance;\r
+  }\r
+\r
+  public static void createGroups(){\r
+\r
+    MemoryDatabase.createMechUserIfNotExists();\r
+    List<String> groupNames = new ArrayList<>(Arrays.asList("Group1", "Group2", "Group3", "Group4", "Group5"));\r
+    groupNames.forEach(name->{\r
+      Group group = new Group();\r
+      User usr = mongoTemplate.findOne(userQuery, User.class);\r
+      group.setOwnerId(usr.get_id());\r
+      group.setGroupName(name);\r
+      group.setGroupDescription(name + " description");\r
+      mongoTemplate.save(group, "groups");\r
+    });\r
+\r
+  }\r
+\r
+  public static void createGroupsForPermission()\r
+  {\r
+    Group parentGroup = new Group();\r
+    Group firstChildGroup = new Group();\r
+    Group childOfChildGroup = new Group();\r
+    parentGroup.setGroupName("parent group");\r
+    firstChildGroup.setGroupName("first child group");\r
+    childOfChildGroup.setGroupName("child of child group");\r
+    Role adminRole = new Role();\r
+    Role devRole = new Role();\r
+    Role executorRole = new Role();\r
+    GroupMember parentMember = new GroupMember();\r
+    GroupMember firstChildMember = new GroupMember();\r
+    GroupMember childOfChildMember = new GroupMember();\r
+    //set up members\r
+    createUsers();\r
+    List<User> users = mongoTemplate.findAll(User.class,"users"); // this should be atleast 3 users\r
+    /*\r
+    set up\r
+    parent group -> members only with admin roles. Permission = "READ","WRITE","MANAGEMENT"\r
+    child group -> members only with admin and dev roles. Permission = "READ","WRITE", "MANAGEMENT\r
+    child of child group -> members with only executor roles. Permission = "EXECUTE\r
+     */\r
+    parentMember.setUserId(users.get(0).get_id());\r
+    parentMember.setRoles(Arrays.asList("admin"));\r
+    firstChildMember.setUserId(users.get(1).get_id());\r
+    firstChildMember.setRoles(Arrays.asList("dev","admin"));\r
+    childOfChildMember.setUserId(users.get(2).get_id());\r
+    childOfChildMember.setRoles(Arrays.asList("executor"));\r
+    //set up roles\r
+    adminRole.setRoleName("admin");\r
+    adminRole.setPermissions(Arrays.asList("READ","WRITE","MANAGEMENT"));\r
+    devRole.setRoleName("dev");\r
+    devRole.setPermissions(Arrays.asList("READ","WRITE"));\r
+    executorRole.setRoleName("executor");\r
+    executorRole.setPermissions(Arrays.asList("EXECUTE"));\r
+    List<Role> defaultRoles = new ArrayList<>();\r
+    defaultRoles.add(devRole);\r
+    defaultRoles.add(adminRole);\r
+    defaultRoles.add(executorRole);\r
+    //set up groups\r
+    parentGroup.setRoles(defaultRoles);\r
+    parentGroup.setMembers(Arrays.asList(parentMember));\r
+    firstChildGroup.setRoles(defaultRoles);\r
+    firstChildGroup.setMembers(Arrays.asList(firstChildMember));\r
+    childOfChildGroup.setRoles(defaultRoles);\r
+    childOfChildGroup.setMembers(Arrays.asList(childOfChildMember));\r
+    /*\r
+      set up parent tree\r
+      structure:\r
+      parentGroup\r
+          |\r
+      Child group\r
+          |\r
+      Child of child group\r
+     */\r
+    mongoTemplate.save(parentGroup,"groups");\r
+    mongoTemplate.save(firstChildGroup,"groups");\r
+    mongoTemplate.save(childOfChildGroup,"groups");\r
+    // query object so we can get the object id and set up parent ids\r
+    Query parentQ = new Query(Criteria.where("groupName").is("parent group"));\r
+    Query firstChildQ = new Query(Criteria.where("groupName").is("first child group"));\r
+    Query childOfChildQ = new Query(Criteria.where("groupName").is("child of child group"));\r
+    Group parentGroupDbObj = mongoTemplate.findOne(parentQ,Group.class);\r
+    Group firstChildDbObj = mongoTemplate.findOne(firstChildQ,Group.class);\r
+    Group childOfChildDbObj = mongoTemplate.findOne(childOfChildQ,Group.class);\r
+\r
+    firstChildDbObj.setParentGroupId(parentGroupDbObj.get_id());\r
+    childOfChildDbObj.setParentGroupId(firstChildDbObj.get_id());\r
+    mongoTemplate.save(firstChildDbObj);\r
+    mongoTemplate.save(childOfChildDbObj);\r
+  }\r
+\r
+  public static void createUsers(){\r
+    List<String> usersFirstNames = new ArrayList<>(Arrays.asList("Joe", "Jim", "Rick", "David", "Tony"));\r
+    List<String> usersLastNames = new ArrayList<>(Arrays.asList("Terry", "Roll", "Luis", "Perry"));\r
+      usersFirstNames.forEach(name->{\r
+        User user = new User();\r
+        int index = new Random().nextInt(usersFirstNames.size()-1);\r
+        user.setEmail(name+usersLastNames.get(index)+"@email.com");\r
+        user.setLastName(name);\r
+        user.setFirstName(usersLastNames.get(index));\r
+        mongoTemplate.save(user, "users");\r
+    });\r
+\r
+  }\r
+  public static void createTeatHeads(){\r
+    List<String> testheadNames = new ArrayList<>(Arrays.asList("SSH", "FTP", "PING", "PROCESS", "daRudeSandstorm"));\r
+    testheadNames.forEach(name->{\r
+      String random = Integer.toString(new Random().nextInt(4000)+4000);\r
+      TestHead testHead = new TestHead();\r
+      testHead.setTestHeadName(name);\r
+      testHead.setTestHeadDescription(name+" virtual test head ");\r
+      testHead.setPort(random);\r
+      testHead.setResourcePath("resources.vths.com/"+name);\r
+      testHead.setHostname("resources.vths.com");\r
+      testHead.setGroupId(createMechUserIfNotExists().get_id());\r
+      testHead.setGroupId(createMechGroupIfNotExists().get_id());\r
+      testHead.setCreatedAt(new Timestamp(new Date().getTime()));\r
+      testHead.setUpdatedAt(new Timestamp(new Date().getTime()));\r
+      mongoTemplate.save(testHead, "testHeads");\r
+\r
+    });\r
+  }\r
+  public static void createTestDefinitions(){\r
+    List<String> testDefinitionNames = new ArrayList<>(Arrays.asList("testDef1", "testDef2", "testDef3", "testDef4"));\r
+    testDefinitionNames.forEach(name->{\r
+      TestDefinition testDefinition = new TestDefinition();\r
+      testDefinition.setDisabled(false);\r
+      testDefinition.setTestName(name);\r
+      testDefinition.setTestDescription(name+" description");\r
+      testDefinition.setProcessDefinitionKey(name+"key");\r
+      testDefinition.setCreatedBy(createMechUserIfNotExists().get_id());\r
+      testDefinition.setGroupId(createMechGroupIfNotExists().get_id());\r
+      testDefinition.setCreatedAt(new Timestamp(new Date().getTime()));\r
+      testDefinition.setUpdatedAt(new Timestamp(new Date().getTime()));\r
+      mongoTemplate.save(testDefinition, "testDefinitions");\r
+    });\r
+  }\r
+  public static void createTestInstances(){\r
+    List<String> testInstanceName = new ArrayList<>(Arrays.asList("TestInstance1", "TestInstance2", "TestInstance3", "TestInstance4"));\r
+    testInstanceName.forEach(name->{\r
+      TestInstance testInstance = new TestInstance();\r
+      testInstance.setTestInstanceName(name);\r
+      testInstance.setTestInstanceDescription(name+" description");\r
+      testInstance.setCreatedBy(createMechUserIfNotExists().get_id());\r
+      testInstance.setGroupId(createMechGroupIfNotExists().get_id());\r
+      testInstance.setTestDefinitionId(createMechTestDefinitionIfNotExists().get_id());\r
+      testInstance.setMaxExecutionTimeInMillis(new Random().nextInt(5000));\r
+      testInstance.setUseLatestTestDefinition(true);\r
+      mongoTemplate.save(testInstance, "testInstances");\r
+    });\r
+  }\r
+\r
+  public static void createTestExecutions(){\r
+    List<String> results = new ArrayList<>(Arrays.asList("COMPLETED", "FAILED", "PASSED", "INCOMPLETE"));\r
+    results.forEach(result->{\r
+      TestExecution testExecution = new TestExecution();\r
+      testExecution.setAsync(false);\r
+      testExecution.setExecutorId(createMechUserIfNotExists().get_id());\r
+      testExecution.setGroupId(createMechGroupIfNotExists().get_id());\r
+      testExecution.setStartTime(new Timestamp(new Date().getTime()));\r
+      testExecution.setEndTime(new Timestamp(DateUtils.addHours(new Date(),3).getTime()));\r
+      testExecution.setTestResult(result);\r
+      testExecution.setTestResultMessage("Process result is: "+ result);\r
+      mongoTemplate.save(testExecution, "testExecutions");\r
+    });\r
+  }\r
+\r
+  public static void createAllAdmin(){\r
+    createMechTestDefinitionIfNotExists();\r
+    createMechTestInstanceIfNotExists();\r
+  }\r
+\r
+  public static void createAllTables()throws Exception{\r
+    setup();\r
+    createUsers();\r
+    createGroups();\r
+    createTeatHeads();\r
+    createTestDefinitions();\r
+    createTestInstances();\r
+    createTestExecutions();\r
+  }\r
+\r
+  public static void cleanup(){\r
+    mongodExecutable.stop();\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/DefinitionTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/DefinitionTest.java
new file mode 100644 (file)
index 0000000..be607d1
--- /dev/null
@@ -0,0 +1,82 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models;\r
+\r
+import org.oran.otf.common.model.TestDefinition;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class DefinitionTest {\r
+  private static TestDefinition testDefinition;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    testDefinition = new TestDefinition();\r
+  }\r
+\r
+  @Test\r
+  public void testDefinitionHasTestNameField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("testName");\r
+  }\r
+\r
+  @Test\r
+  public void testDefinitionHasTestDescriptionField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("testDescription");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasProcessDefinitionKeyField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("processDefinitionKey");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasBpmnInstancesField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("bpmnInstances");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasGroupIdField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("groupId");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasCreatedAtField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("createdAt");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasUpdateAtField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("updatedAt");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasCreatedByField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("createdBy");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasUpdatedByField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("updatedBy");\r
+  }\r
+  @Test\r
+  public void testDefinitionHasDisabledField(){\r
+    Assertions.assertThat(testDefinition).hasFieldOrProperty("disabled");\r
+  }\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/ExecutionTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/ExecutionTest.java
new file mode 100644 (file)
index 0000000..966ee76
--- /dev/null
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models;\r
+\r
+import org.oran.otf.common.model.TestExecution;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class ExecutionTest {\r
+  private static TestExecution testExecution;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    testExecution = new TestExecution();\r
+  }\r
+\r
+  @Test\r
+  public void testExecutionHasGroupIdField(){\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("groupId");\r
+  }\r
+  @Test\r
+  public void testExecutionHasExecutorIdField(){\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("executorId");\r
+  }\r
+  @Test\r
+  public void testExecutionHasAsyncField(){\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("async");\r
+  }\r
+  @Test\r
+  public void testExecutionHasStartTimeField(){\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("startTime");\r
+  }\r
+  @Test\r
+  public void testExecutionHasEndTimeField(){\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("endTime");\r
+  }\r
+  @Test\r
+  public void testExecutionHasAsyncTopicField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("asyncTopic");\r
+  }\r
+  @Test\r
+  public void testExecutionHasBussinessKeyField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("businessKey");\r
+  }\r
+  @Test\r
+  public void testExecutionHasProcessInstanceIdField(){\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("processInstanceId");\r
+  }\r
+  @Test\r
+  public void testExecutionHasTestResultField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("testResult");\r
+  }\r
+  @Test\r
+  public void testExecutionHasTestResultMessageField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("testResultMessage");\r
+  }\r
+  @Test\r
+  public void testExecutionHasTestDetailsField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("testDetails");\r
+  }\r
+  @Test\r
+  public void testExecutionHasTestHeadResultsField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("testHeadResults");\r
+  }\r
+  @Test\r
+  public void testExecutionHasTestInstanceResultsField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("testInstanceResults");\r
+  }\r
+  @Test\r
+  public void testExecutionHasHistoricEmailField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("historicEmail");\r
+  }\r
+  @Test\r
+  public void testExecutionHasHistoricTestInstanceField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("historicTestInstance");\r
+  }\r
+  @Test\r
+  public void testExecutionHasHistoricTestDefinitionField(){\r
+\r
+    Assertions.assertThat(testExecution).hasFieldOrProperty("historicTestDefinition");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/GroupTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/GroupTest.java
new file mode 100644 (file)
index 0000000..c28a406
--- /dev/null
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models;\r
+\r
+import static org.assertj.core.api.Assertions.assertThat;\r
+\r
+import org.oran.otf.common.model.Group;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+\r
+public class GroupTest {\r
+  private static Group group;\r
+  @BeforeClass\r
+  public static void setup(){\r
+    group = new Group();\r
+  }\r
+  @Test\r
+  public void testGroupHasNameField(){\r
+    assertThat(group).hasFieldOrProperty("groupName");\r
+  }\r
+  @Test\r
+  public void testGroupHasGroupDescriptionField(){\r
+    assertThat(group).hasFieldOrProperty("groupDescription");\r
+  }\r
+\r
+  @Test\r
+  public void testGroupHasMechanizedIdsField(){\r
+    assertThat(group).hasFieldOrProperty("mechanizedIds");\r
+  }\r
+\r
+  @Test\r
+  public void testGroupHasOwnerIdField(){\r
+    assertThat(group).hasFieldOrProperty("ownerId");\r
+  }\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/HeadTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/HeadTest.java
new file mode 100644 (file)
index 0000000..00ba7ca
--- /dev/null
@@ -0,0 +1,72 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models;\r
+\r
+import org.oran.otf.common.model.TestHead;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class HeadTest {\r
+  private static TestHead testHead;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    testHead = new TestHead();\r
+  }\r
+  @Test\r
+  public void testHeadHasTestHeadNameField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("testHeadName");\r
+  }\r
+  @Test\r
+  public void testHeadHasTestHeadDescriptionField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("testHeadDescription");\r
+  }\r
+  @Test\r
+  public void testHeadHasHostNameField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("hostname");\r
+  }\r
+  @Test\r
+  public void testHeadHasPortField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("port");\r
+  }\r
+  @Test\r
+  public void testHeadHasResourcePathField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("resourcePath");\r
+  }\r
+  @Test\r
+  public void testHeadHasCreatorIdField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("creatorId");\r
+  }\r
+  @Test\r
+  public void testHeadHasGroupIdField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("groupId");\r
+  }\r
+  @Test\r
+  public void testHeadHasCreatedAtField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("createdAt");\r
+  }\r
+  @Test\r
+  public void testHeadHasUpdatedAtField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("updatedAt");\r
+  }\r
+  @Test\r
+  public void testHeadHasUpdatedByField(){\r
+    Assertions.assertThat(testHead).hasFieldOrProperty("updatedBy");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/InstanceTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/InstanceTest.java
new file mode 100644 (file)
index 0000000..3f1a4be
--- /dev/null
@@ -0,0 +1,104 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models;\r
+\r
+import org.oran.otf.common.model.TestInstance;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class InstanceTest {\r
+\r
+  private static TestInstance testInstance;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    testInstance = new TestInstance();\r
+  }\r
+  @Test\r
+  public void testInstanceHasTestInstanceNameField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("testInstanceName");\r
+  }\r
+  @Test\r
+  public void testInstanceHasInstanceDescriptionField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("testInstanceDescription");\r
+  }\r
+  @Test\r
+  public void testInstanceHasGroupIdField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("groupId");\r
+  }\r
+  @Test\r
+  public void testInstanceHasTestDefinitionIdField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("testDefinitionId");\r
+  }\r
+  @Test\r
+  public void testInstanceHasProcessDefinitionIdField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("processDefinitionId");\r
+  }\r
+  @Test\r
+  public void testInstanceHasUseLatestTestDefinitionField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("useLatestTestDefinition");\r
+  }\r
+  @Test\r
+  public void testInstanceHasDisabledField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("disabled");\r
+  }\r
+  @Test\r
+  public void testInstanceHasSimulationModeField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("simulationMode");\r
+  }\r
+  @Test\r
+  public void testInstanceHasMaxExecutionTimeInMillisField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("maxExecutionTimeInMillis");\r
+  }\r
+  @Test\r
+  public void testInstanceHasPfloInputField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("pfloInput");\r
+  }\r
+  @Test\r
+  public void testInstanceHasInternalTestDataField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("internalTestData");\r
+  }\r
+  @Test\r
+  public void testInstanceHasSimulationVthInputField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("simulationVthInput");\r
+  }\r
+  @Test\r
+  public void testInstanceHasTestDataField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("testData");\r
+  }\r
+  @Test\r
+  public void testInstanceHasVthInputField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("vthInput");\r
+  }\r
+  @Test\r
+  public void testInstanceHasCreatedAtField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("createdAt");\r
+  }\r
+  @Test\r
+  public void testInstanceHasUpdatedAtField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("updatedAt");\r
+  }\r
+  @Test\r
+  public void testInstanceHasCreatedByField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("createdBy");\r
+  }\r
+  @Test\r
+  public void testInstanceHasUpdatedByField(){\r
+    Assertions.assertThat(testInstance).hasFieldOrProperty("updatedBy");\r
+  }\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/UserTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/UserTest.java
new file mode 100644 (file)
index 0000000..49fb31c
--- /dev/null
@@ -0,0 +1,63 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models;\r
+\r
+import org.oran.otf.common.model.User;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class UserTest {\r
+\r
+  private static User user;\r
+  @BeforeClass\r
+  public static void setup(){\r
+    user = new User();\r
+  }\r
+  @Test\r
+  public void testUserHasPermissionsField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("permissions");\r
+  }\r
+  @Test\r
+  public void testUserHasFirstNameField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("firstName");\r
+  }\r
+  @Test\r
+  public void testUserHasLastNameField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("lastName");\r
+  }\r
+  @Test\r
+  public void testUserHasEmailField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("email");\r
+  }\r
+  @Test\r
+  public void testUserHasPasswordField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("password");\r
+  }\r
+  @Test\r
+  public void testUserHasGroupsField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("groups");\r
+  }\r
+  @Test\r
+  public void testUserHasCreatedAtField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("createdAt");\r
+  }\r
+  @Test\r
+  public void testUserHasUpdatedAtField(){\r
+    Assertions.assertThat(user).hasFieldOrProperty("updatedAt");\r
+  }\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/BpmnTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/BpmnTest.java
new file mode 100644 (file)
index 0000000..2f7f166
--- /dev/null
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.BpmnInstance;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class BpmnTest {\r
+  private static BpmnInstance bpmnInstance;\r
+  @BeforeClass\r
+  public static void setup(){\r
+    bpmnInstance = new BpmnInstance();\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasProcessDefinitionIdField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("processDefinitionId");\r
+  }\r
+  @Test\r
+  public   void testBpmnInstanceHasDeploymentIdField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("deploymentId");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasVersionField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("version");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasBpmnFileIdField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("bpmnFileId");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasResourceFileIdField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("resourceFileId");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasIsDeployedField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("isDeployed");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasTestHeadsField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("testHeads");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasPflowsField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("pflos");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasTestDataTemplateField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("testDataTemplate");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasCreatedAtField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("createdAt");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceUpdatedAtField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("updatedAt");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasCreatedByField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("createdBy");\r
+  }\r
+  @Test\r
+  public  void testBpmnInstanceHasUpdatedByField(){\r
+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("updatedBy");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/DeployTestStrategyRequestTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/DeployTestStrategyRequestTest.java
new file mode 100644 (file)
index 0000000..b41c4ee
--- /dev/null
@@ -0,0 +1,44 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.DeployTestStrategyRequest;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class DeployTestStrategyRequestTest {\r
+  private static DeployTestStrategyRequest deployTestStrategyRequest;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    deployTestStrategyRequest = new DeployTestStrategyRequest();\r
+  }\r
+  @Test\r
+  public void testDeployTestStrategyRequestHasTestDefinitionDeployerIdField(){\r
+    Assertions.assertThat(deployTestStrategyRequest).hasFieldOrProperty("testDefinitionDeployerId");\r
+  }\r
+  @Test\r
+  public void testDeployTestStrategyRequestHasTestDefinitionIdField(){\r
+    Assertions.assertThat(deployTestStrategyRequest).hasFieldOrProperty("TestDefinitionId");\r
+  }\r
+  @Test\r
+  public void testDeployTestStrategyRequestHasDefinitionIdField(){\r
+    Assertions.assertThat(deployTestStrategyRequest).hasFieldOrProperty("DefinitionId");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadNodeTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadNodeTest.java
new file mode 100644 (file)
index 0000000..fe0ce0f
--- /dev/null
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.TestHeadNode;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class HeadNodeTest {\r
+  private static TestHeadNode testHeadNode;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    testHeadNode = new TestHeadNode();\r
+  }\r
+  @Test\r
+  public void testHeadNodeHasTestHeadIdField(){\r
+    Assertions.assertThat(testHeadNode).hasFieldOrProperty("testHeadId");\r
+  }\r
+  @Test\r
+  public void testHeadNodeHasBpmnVthTaskIdField(){\r
+    Assertions.assertThat(testHeadNode).hasFieldOrProperty("bpmnVthTaskId");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadResultTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadResultTest.java
new file mode 100644 (file)
index 0000000..1fde494
--- /dev/null
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.TestHeadResult;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class HeadResultTest {\r
+  private static TestHeadResult testHeadResult;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    testHeadResult = new TestHeadResult();\r
+  }\r
+  @Test\r
+  public void testHeadResultHasTestHeadIdField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadId");\r
+  }\r
+  @Test\r
+  public void testHeadResultHasTestHeadNameField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadName");\r
+  }\r
+  @Test\r
+  public void testHeadResultHasBpmnVthTaskIdField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("bpmnVthTaskId");\r
+  }\r
+  @Test\r
+  public void testHeadResultHasTestHeadRequestField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadRequest");\r
+  }\r
+  @Test\r
+  public void testHeadResultHasTestHeadResponseField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadResponse");\r
+  }\r
+  @Test\r
+  public void testHeadResultHasStartTimeField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("startTime");\r
+  }\r
+  @Test\r
+  public void testHeadResultHasEndTimeField(){\r
+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("endTime");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/InstanceCreateRequestTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/InstanceCreateRequestTest.java
new file mode 100644 (file)
index 0000000..83fdf09
--- /dev/null
@@ -0,0 +1,93 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.TestInstanceCreateRequest;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class InstanceCreateRequestTest {\r
+  private static TestInstanceCreateRequest testInstanceCreateRequest;\r
+\r
+  @BeforeClass\r
+  public static void setup() throws Exception{\r
+    //No Argument Constructor does not work because of the requiered name when creating\r
+    testInstanceCreateRequest = new TestInstanceCreateRequest(\r
+        "Name",\r
+        "Description",\r
+        null,\r
+        null,\r
+        null,\r
+        null,\r
+        null,\r
+        true,\r
+        false,\r
+        0L\r
+    );\r
+  }\r
+\r
+  @Test\r
+  public void testInstanceCreateRequestHasTestDefinitionIdField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("testDefinitionId");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasVersionField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("version");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasProcessDefinitionKeyField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("processDefinitionKey");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHastestInstanceNameField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("testInstanceName");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasPfloInputField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("pfloInput");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasSimulationVthInputField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("simulationVthInput");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasTestDataField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("testData");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasVthInputField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("vthInput");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasCreatedByField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("createdBy");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasUseLatestTestDefinitionField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("useLatestTestDefinition");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasSimulationModeField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("simulationMode");\r
+  }\r
+  @Test\r
+  public void testInstanceCreateRequestHasMaxExecutionTimeInMillisField(){\r
+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("maxExecutionTimeInMillis");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/OtfApiResponseTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/OtfApiResponseTest.java
new file mode 100644 (file)
index 0000000..45c10a4
--- /dev/null
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class OtfApiResponseTest {\r
+  private static OTFApiResponse otfApiResponse;\r
+  @BeforeClass\r
+  public static void setup(){\r
+    otfApiResponse = new OTFApiResponse();\r
+  }\r
+  @Test\r
+  public void testOtfApiResponseHasStatusCodeField(){\r
+    Assertions.assertThat(otfApiResponse).hasFieldOrProperty("statusCode");\r
+  }\r
+  @Test\r
+  public void testOtfApiResponseHasMessageField(){\r
+    Assertions.assertThat(otfApiResponse).hasFieldOrProperty("message");\r
+  }\r
+  @Test\r
+  public void testOtfApiResponseHasTimeField(){\r
+    Assertions.assertThat(otfApiResponse).hasFieldOrProperty("time");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/ParallelFlowInputTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/ParallelFlowInputTest.java
new file mode 100644 (file)
index 0000000..320df54
--- /dev/null
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.ParallelFlowInput;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class ParallelFlowInputTest {\r
+  private static ParallelFlowInput parallelFlowInput;\r
+  @BeforeClass\r
+  public static void setup(){\r
+    parallelFlowInput = new ParallelFlowInput();\r
+  }\r
+  @Test\r
+  public void testParallelFlowInputHasArgsField(){\r
+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("args");\r
+  }\r
+  @Test\r
+  public void testParallelFlowInputHasInterruptOnFailureField(){\r
+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("interruptOnFailure");\r
+  }\r
+  @Test\r
+  public void testParallelFlowInputHasMaxFailuresField(){\r
+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("maxFailures");\r
+  }\r
+  @Test\r
+  public void testParallelFlowInputHasThreadPoolSizeField(){\r
+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("threadPoolSize");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/PfloNodeTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/PfloNodeTest.java
new file mode 100644 (file)
index 0000000..7a0a20b
--- /dev/null
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.PfloNode;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class PfloNodeTest {\r
+  private static PfloNode pfloNode;\r
+\r
+  @BeforeClass\r
+  public static void setup(){\r
+    pfloNode = new PfloNode();\r
+  }\r
+  @Test\r
+  public void testPfloNodeHasBpmnPfloTaskIdField(){\r
+    Assertions.assertThat(pfloNode).hasFieldOrProperty("bpmnPlfoTaskId");\r
+  }\r
+  @Test\r
+  public void testPfloNodeHasLabelField(){\r
+    Assertions.assertThat(pfloNode).hasFieldOrProperty("label");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/UserGroupTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/UserGroupTest.java
new file mode 100644 (file)
index 0000000..92e7497
--- /dev/null
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.UserGroup;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class UserGroupTest {\r
+  private static UserGroup userGroup;\r
+\r
+  //TODO (DONE): Added NoArg Constructor to UserGroup model for testing\r
+  @BeforeClass\r
+  public static void setup(){\r
+    userGroup = new UserGroup();\r
+  }\r
+\r
+  @Test\r
+  public void testUserGroupHasGroupIdField(){\r
+    Assertions.assertThat(userGroup).hasFieldOrProperty("groupId");\r
+  }\r
+  @Test\r
+  public void testUserGroupHasPermissionsField(){\r
+    Assertions.assertThat(userGroup).hasFieldOrProperty("permissions");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/WorkFlowRequestTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/WorkFlowRequestTest.java
new file mode 100644 (file)
index 0000000..6debf07
--- /dev/null
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.models.local;\r
+\r
+import org.oran.otf.common.model.local.WorkflowRequest;\r
+import org.assertj.core.api.Assertions;\r
+import org.junit.BeforeClass;\r
+import org.junit.Test;\r
+\r
+public class WorkFlowRequestTest {\r
+  private static WorkflowRequest workflowRequest;\r
+\r
+  @BeforeClass\r
+  public static void setup()throws Exception{\r
+    workflowRequest = new WorkflowRequest();\r
+  }\r
+\r
+  @Test\r
+  public void testWorkFlowRequestHasAsyncField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("async");\r
+  }\r
+  @Test\r
+  public void testWorkFlowRequestHasExecutorIdField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("executorId");\r
+  }\r
+  @Test\r
+  public void testWorkFlowRequestHasTestInstanceIdField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("testInstanceId");\r
+  }\r
+  @Test\r
+  public void testWorkFlowRequestHasPfloInputField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("pfloInput");\r
+  }\r
+  @Test\r
+  public void testWorkFlowRequestHasTestDataField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("testData");\r
+  }\r
+  @Test\r
+  public void testWorkFlowRequestHasVthInputField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("vthInput");\r
+  }\r
+  @Test\r
+  public void testWorkFlowRequestHasMaxExecutionTimeInMillisField(){\r
+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("maxExecutionTimeInMillis");\r
+  }\r
+\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/BuildResponseTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/BuildResponseTest.java
new file mode 100644 (file)
index 0000000..15afaa3
--- /dev/null
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.utility;\r
+\r
+import org.oran.otf.api.Utilities;\r
+import org.oran.otf.common.model.local.OTFApiResponse;\r
+import org.junit.Assert;\r
+import org.junit.Test;\r
+\r
+import javax.ws.rs.core.Response;\r
+\r
+public class BuildResponseTest {\r
+    @Test\r
+    public void badResponseTest(){\r
+        Response badResponse = Utilities.Http.BuildResponse.badRequest();\r
+        Assert.assertNotNull(badResponse);\r
+        Assert.assertEquals(badResponse.getStatus(),400);\r
+    }\r
+\r
+    @Test\r
+    public void badRequestWithMessageTest() {\r
+        Response badResponse = Utilities.Http.BuildResponse.badRequestWithMessage("this is bad");\r
+        OTFApiResponse response = (OTFApiResponse) badResponse.getEntity();\r
+\r
+        Assert.assertNotNull(badResponse);\r
+        Assert.assertEquals(badResponse.getStatus(),400);\r
+        Assert.assertEquals(response.getStatusCode(), 400);\r
+        Assert.assertEquals(response.getMessage(), "this is bad");\r
+    }\r
+    @Test\r
+    public void internalServerErrorTest(){\r
+        Response badResponse = Utilities.Http.BuildResponse.internalServerError();\r
+        Assert.assertNotNull(badResponse);\r
+        Assert.assertEquals(badResponse.getStatus(),500);\r
+    }\r
+    @Test\r
+    public void internalServerErrorWithMessageTest(){\r
+        Response badResponse = Utilities.Http.BuildResponse.internalServerErrorWithMessage("internal error");\r
+        OTFApiResponse response = (OTFApiResponse) badResponse.getEntity();\r
+\r
+        Assert.assertNotNull(badResponse);\r
+        Assert.assertEquals(badResponse.getStatus(),500);\r
+        Assert.assertEquals(response.getStatusCode(), 500);\r
+        Assert.assertEquals(response.getMessage(), "internal error");\r
+    }\r
+\r
+    @Test\r
+    public void unauthorizedTest(){\r
+        Response basicUnauthorizedResponse=  Utilities.Http.BuildResponse.unauthorized();\r
+        Response unauthorizedMsgResponse = Utilities.Http.BuildResponse.unauthorizedWithMessage("unauthorized");\r
+        OTFApiResponse response = (OTFApiResponse) unauthorizedMsgResponse.getEntity();\r
+\r
+        Assert.assertNotNull(basicUnauthorizedResponse);\r
+        Assert.assertNotNull(unauthorizedMsgResponse);\r
+        Assert.assertEquals(basicUnauthorizedResponse.getStatus(),401);\r
+        Assert.assertEquals(unauthorizedMsgResponse.getStatus(),401);\r
+        Assert.assertEquals(response.getStatusCode(),401);\r
+        Assert.assertEquals(response.getMessage(),"unauthorized");\r
+    }\r
+}\r
diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/UserPermissionTest.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/UserPermissionTest.java
new file mode 100644 (file)
index 0000000..e2d7954
--- /dev/null
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.tests.unit.utility;\r
+\r
+import org.oran.otf.common.utility.permissions.UserPermission;\r
+import org.junit.Assert;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+import org.junit.runner.RunWith;\r
+import org.mockito.InjectMocks;\r
+import org.mockito.Mock;\r
+import org.mockito.Mockito;\r
+import org.mockito.junit.MockitoJUnitRunner;\r
+\r
+import java.util.*;\r
+\r
+@RunWith(MockitoJUnitRunner.class)\r
+public class UserPermissionTest {\r
+\r
+    @Mock\r
+    Map<String, Set<String>> userAccessMap ;\r
+\r
+    @InjectMocks\r
+    private UserPermission userPermission;\r
+\r
+    @Before\r
+    public void setUp()\r
+    {\r
+        String fakeGroupId1 = "abc123";\r
+        Set<String> user1Permissions = new HashSet<>(Arrays.asList("READ","WRITE"));\r
+        Mockito.when(userAccessMap.get(fakeGroupId1)).thenReturn(user1Permissions);\r
+    }\r
+\r
+    @Test\r
+    public void testHasAccessToMethod(){\r
+\r
+        Assert.assertNotNull(userPermission.getUserAccessMap());\r
+        //test when user have access to group with certain permissions and a fake permission(mix of upper and lower case\r
+        Assert.assertTrue(userPermission.hasAccessTo("abc123","READ"));\r
+        Assert.assertTrue(userPermission.hasAccessTo("abc123","WrIte"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("abc123","DEleTE"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("abc123","ExECUTe"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("abc123","mANAgEMENT"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("abc123","READ+WRITE"));\r
+\r
+        //test when user have no access to the group\r
+        Assert.assertFalse(userPermission.hasAccessTo("edf567","READ"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("edf567","WRITE"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("edf567","DELETE"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("edf567","EXECUTE"));\r
+        Assert.assertFalse(userPermission.hasAccessTo("edf567","MANAGEMENT"));\r
+    }\r
+}\r
diff --git a/otf-service-api/src/test/resources/application-test.properties b/otf-service-api/src/test/resources/application-test.properties
new file mode 100644 (file)
index 0000000..a0a7d2a
--- /dev/null
@@ -0,0 +1,19 @@
+server.port=8443\r
+server.port.http=8181\r
+\r
+otf.mongo.hosts=${OTF_MONGO_HOSTS}\r
+otf.mongo.username=${OTF_MONGO_USERNAME}\r
+otf.mongo.password=${OTF_MONGO_PASSWORD}\r
+otf.mongo.replicaSet=${OTF_MONGO_REPLICASET}\r
+otf.mongo.database=${OTF_MONGO_DATABASE}\r
+\r
+cadi.prop.files=src/main/resources/cadi.properties\r
+\r
+otf.proxy=localhost\r
+otf.proxy-port=8080\r
+otf.embedded.host=localhost\r
+otf.embedded.port=5555\r
+otf.embedded.database=otf\r
+\r
+otf.mechid=${AAF_ID}\r
+otf.mechpass=${AAF_MECH_PASSWORD}\r
diff --git a/otf-service-api/swagger.json b/otf-service-api/swagger.json
new file mode 100644 (file)
index 0000000..2f5f6c8
--- /dev/null
@@ -0,0 +1 @@
+{"openapi":"3.0.1","info":{"title":"Open Test Framework API","description":"A RESTful API used to communicate with the OTF test control unit.","contact":{"name":"OTF","url":"https://localhost:32524"},"version":"1.0"},"tags":[{"name":"Health Service","description":"Query the availability of the API"},{"name":"Test Execution Service","description":"Query the status and history of your test executions"},{"name":"Test Instance Service","description":"Create, execute, and query test instances"},{"name":"Test Strategy Service","description":"Deploy and delete test strategies to and from the test control unit. (This documentation will only be available to the development team)"}],"paths":{"/otf/api/health/v1":{"get":{"tags":["Health Service"],"summary":"Checks if the test control unit is available","operationId":"getHealth_1","responses":{"200":{"description":"The test control unit is available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse"}}}}}}},"/otf/api/testExecution/v1/executionId/{executionId}":{"get":{"tags":["Test Execution Service"],"operationId":"getExecutionStatus_1","parameters":[{"name":"executionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","schema":{"type":"string"}}],"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/testInstance/execute/v1/id/{testInstanceId}":{"post":{"tags":["Test Instance Service"],"summary":"Executes a test instance by it's unique identifier","operationId":"execute_1","parameters":[{"name":"testInstanceId","in":"path","description":"A string representation of a BSON ObjectId","required":true,"schema":{"type":"string","description":"The UUID of the test instance","format":"uuid"},"example":"12345678912345678912345f"},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecuteTestInstanceRequest"}}}},"responses":{"200":{"description":"A successful synchronously executed test returns a test execution object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestExecutionResult"}}}},"201":{"description":"A successful asynchronously executed test with asyncMode set to 'poll' returns an execution identifier\nThe identifier can be used as a parameter to the Test Execution Service to check the status of the executed test","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestExecutionResult"}}}},"401":{"description":"The mechanized identifier used with the request is prohibited from accessing the resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse"}}}}}}},"/otf/api/testInstance/v1/id/{id}":{"get":{"tags":["Test Instance Service"],"operationId":"findById_1","parameters":[{"name":"id","in":"path","description":"A string representation of a BSON ObjectId","required":true,"schema":{"type":"string","description":"The UUID of the test instance","format":"uuid"},"example":"12345678912345678912345f"},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="}],"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/testInstance/create/v1/testDefinitionId/{testDefinitionId}/version/{version}":{"post":{"tags":["Test Instance Service"],"summary":"Create a test instance using the specified version of the test definition","operationId":"createByTestDefinitionIdAndVersion_1","parameters":[{"name":"testDefinitionId","in":"path","description":"A string representation of a BSON ObjectId","required":true,"schema":{"type":"string","description":"The UUID of the test definition.","format":"uuid"},"example":"12345678912345678912345f"},{"name":"version","in":"path","description":"The version of the test definition used to create the instance","required":true,"schema":{"type":"string"},"example":2},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="},{"name":"execute","in":"query","description":"Execute the test instance after it is created","allowEmptyValue":true,"schema":{"type":"boolean"},"example":true}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestInstanceRequest"}}}},"responses":{"201":{"description":"The created Test Instance object is returned when it is created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestInstance"}}}}}}},"/otf/api/testInstance/v1/testInstanceName/{testInstanceName}":{"get":{"tags":["Test Instance Service"],"summary":"Finds a test instance by it's name","operationId":"findByTestInstanceName_1","parameters":[{"name":"testInstanceName","in":"path","description":"The name of the test instance to retrieve","required":true,"schema":{"type":"string"},"example":"myTestInstance"},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="}],"responses":{"200":{"description":"A test instance object is returned when if it is found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestInstance"}}}}}}},"/otf/api/testInstance/v1/processDefinitionKey/{processDefinitionKey}":{"get":{"tags":["Test Instance Service"],"operationId":"findByProcessDefKey_1","parameters":[{"name":"processDefinitionKey","in":"path","description":"The process definition key associated with the test definition","required":true,"schema":{"type":"string"},"example":"someUniqueProcessDefinitionKey"},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="}],"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/testInstance/create/v1/testDefinitionId/{testDefinitionId}":{"post":{"tags":["Test Instance Service"],"summary":"Create a test instance using the latest version of the test definition","operationId":"createByTestDefinitionId_1","parameters":[{"name":"testDefinitionId","in":"path","description":"A string representation of a BSON ObjectId","required":true,"schema":{"type":"string","description":"The UUID of the test definition","format":"uuid"},"example":"12345678912345678912345f"},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="},{"name":"execute","in":"query","description":"Execute the test instance after it is created","allowEmptyValue":true,"schema":{"type":"boolean"},"example":true}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestInstanceRequest"}}}},"responses":{"201":{"description":"The created Test Instance object is returned when it is created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestInstance"}}}}}}},"/otf/api/testInstance/v1/processDefinitionKey/{processDefinitionKey}/version/{version}":{"get":{"tags":["Test Instance Service"],"operationId":"findByProcessDefKeyAndVersion_1","parameters":[{"name":"processDefinitionKey","in":"path","description":"The process definition key associated with the test definition","required":true,"schema":{"type":"string"},"example":"someUniqueProcessDefinitionKey"},{"name":"version","in":"path","description":"The version of the test definition used to create the instance","required":true,"schema":{"type":"string"},"example":2},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="}],"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/testInstance/create/v1/processDefinitionKey/{processDefinitionKey}/version/{version}":{"post":{"tags":["Test Instance Service"],"summary":"Create a test instance using the specified version of the test definition","operationId":"createByProcessDefKeyAndVersion_1","parameters":[{"name":"processDefinitionKey","in":"path","description":"The process definition key associated with the test definition","required":true,"schema":{"type":"string"},"example":"someUniqueProcessDefinitionKey"},{"name":"version","in":"path","description":"The version of the test definition used to create the instance","required":true,"schema":{"type":"string"},"example":2},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="},{"name":"execute","in":"query","description":"Execute the test instance after it is created","allowEmptyValue":true,"schema":{"type":"boolean"},"example":true}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestInstanceRequest"}}}},"responses":{"201":{"description":"The created Test Instance object is returned when it is created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestInstance"}}}}}}},"/otf/api/testInstance/create/v1/processDefinitionKey/{processDefinitionKey}":{"post":{"tags":["Test Instance Service"],"summary":"Create a test instance using the latest version of the test definition","operationId":"createByProcessDefKey_1","parameters":[{"name":"processDefinitionKey","in":"path","description":"The process definition key associated with the test definition","required":true,"schema":{"type":"string"},"example":"someUniqueProcessDefinitionKey"},{"name":"Authorization","in":"header","description":"Base64 encoded Application Authorization Framework credentials","required":true,"schema":{"type":"string"},"example":"Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM="},{"name":"execute","in":"query","description":"Execute the test instance after it is created","allowEmptyValue":true,"schema":{"type":"boolean"},"example":true}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestInstanceRequest"}}}},"responses":{"201":{"description":"The created Test Instance object is returned when it is created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestInstance"}}}}}}},"/otf/api/testStrategy/delete/v1/deploymentId/{deploymentId}":{"delete":{"tags":["Test Strategy Service"],"operationId":"deleteByDeploymentId_1","parameters":[{"name":"deploymentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"authorization","in":"header","schema":{"type":"string"}}],"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/testStrategy/delete/v1/testDefinitionId/{testDefinitionId}":{"delete":{"tags":["Test Strategy Service"],"operationId":"deleteByTestDefinitionId_1","parameters":[{"name":"testDefinitionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"authorization","in":"header","schema":{"type":"string"}}],"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/testStrategy/deploy/v1":{"post":{"tags":["Test Strategy Service"],"operationId":"deployTestStrategy_1","parameters":[{"name":"Authorization","in":"header","schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"bpmn":{"type":"object"},"resources":{"type":"object"},"testDefinitionId":{"type":"string"},"testDefinitionDeployerId":{"type":"string"},"definitionId":{"type":"string"}}}}}},"responses":{"default":{"description":"default response","content":{"application/json":{}}}}}},"/otf/api/application.wadl/{path}":{"get":{"operationId":"getExternalGrammar","parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"default response","content":{"application/xml":{}}}}}},"/otf/api/application.wadl":{"get":{"operationId":"getWadl","responses":{"default":{"description":"default response","content":{"application/vnd.sun.wadl+xml":{},"application/xml":{}}}}}}},"components":{"schemas":{"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"date":{"type":"string","format":"date-time"},"message":{"type":"string"}}},"JSONObject":{"type":"object"},"ObjectId":{"type":"object","properties":{"timestamp":{"type":"integer","format":"int32"},"machineIdentifier":{"type":"integer","format":"int32"},"processIdentifier":{"type":"integer","format":"int32"},"counter":{"type":"integer","format":"int32"},"time":{"type":"integer","format":"int64"},"date":{"type":"string","format":"date-time"},"timeSecond":{"type":"integer","format":"int32"}}},"TestExecution":{"type":"object","properties":{"get_id":{"$ref":"#/components/schemas/ObjectId"},"executionId":{"type":"string"},"testResult":{"type":"string"},"testDetails":{"type":"object","additionalProperties":{"type":"object"}},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"async":{"type":"boolean"},"asyncTopic":{"type":"string"},"asyncMode":{"type":"string"},"executor":{"type":"string"},"groupId":{"$ref":"#/components/schemas/ObjectId"},"testInstanceId":{"$ref":"#/components/schemas/ObjectId"},"testInstance":{"type":"object","additionalProperties":{"type":"object"}},"testHeadResults":{"type":"array","items":{"$ref":"#/components/schemas/TestHeadResult"}},"testDetailsJSON":{"type":"string"},"testInstanceJSON":{"type":"string"}}},"TestExecutionResult":{"type":"object","properties":{"testExecution":{"$ref":"#/components/schemas/TestExecution"},"executionId":{"type":"string"},"testCompleted":{"type":"boolean"},"testExists":{"type":"boolean"}}},"TestHeadResult":{"type":"object","properties":{"testHeadId":{"$ref":"#/components/schemas/ObjectId"},"testHeadName":{"type":"string"},"bpmnVthTaskId":{"type":"string"},"testHeadResponse":{"type":"object","additionalProperties":{"type":"object"}},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"testHeadResponseJSON":{"$ref":"#/components/schemas/JSONObject"}}},"ExecuteTestInstanceRequest":{"type":"object","properties":{"async":{"type":"boolean","writeOnly":true},"asyncTopic":{"title":"Execute the test synchronously or asynchronously..","type":"string","description":"Ignored unless async is true, and asyncMode is DMaaP.","example":"MyDMaaPTopic."},"asyncMode":{"title":"Set the asynchronous execution mode.","type":"string","description":"Ignored unless async is true. The poll mode will return an executionId that can be used to query the result of the executed test. DMaaP is currently unsupported.","example":"POLL","enum":["POLL","DMAAP"]},"testData":{"title":"Use an existing test instance with different global test data.","type":"object","description":"Overrides (not overwrites) the testData field for the requested execution. The overridden data will be preserved in the test execution result.","example":{"globalVar1":"I'm available to your workflow!","globalVar2":{"me":"too"}}},"vthInput":{"title":"Use an existing test instance with different inputs to your VTHs.","type":"object","description":"Overrides (not overwrites) the vthInput field for the requested execution. The overridden data will be preserved in the test execution result.","example":{"ServiceTask_123":{"vthArg1":"An argument your VTH expects.","vthArg2":{}},"ServiceTask_456":{"vthArg1":"An argument your VTH expects."}}}},"description":"The model for a test instance execution request."},"TestInstance":{"type":"object","properties":{"get_id":{"$ref":"#/components/schemas/ObjectId"},"testInstanceName":{"type":"string"},"testInstanceDescription":{"type":"string"},"groupId":{"$ref":"#/components/schemas/ObjectId"},"testDefinitionId":{"$ref":"#/components/schemas/ObjectId"},"processDefinitionId":{"type":"string"},"useLatestTestDefinition":{"type":"boolean"},"testData":{"type":"object","additionalProperties":{"type":"object"}},"vthInput":{"type":"object","additionalProperties":{"type":"object"}},"internalTestData":{"type":"object","additionalProperties":{"type":"object"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"createdBy":{"$ref":"#/components/schemas/ObjectId"},"updatedBy":{"$ref":"#/components/schemas/ObjectId"},"vthInputJSON":{"$ref":"#/components/schemas/JSONObject"},"testDataJSON":{"$ref":"#/components/schemas/JSONObject"},"internalTestDataJSON":{"$ref":"#/components/schemas/JSONObject"}}},"CreateTestInstanceRequest":{"required":["testData","testInstanceDescription","testInstanceName"],"type":"object","properties":{"testInstanceName":{"title":"Name the test instance","type":"string","description":"The name must be unique among all test instances belonging to the same test definition.","example":"MyTestInstance"},"testInstanceDescription":{"title":"Describe the test instance being created","type":"string","description":"Use this field to describe the functionality of the test instance","example":"This test instance does absolutely nothing!"},"testData":{"title":"Set global variables","type":"object","description":"This field has read and write access by any task within the workflow.\nSee the example for more information","example":{"globalVar1":"I'm available to your workflow!","globalVar2":{"me":"too"}}},"vthInput":{"title":"Set virtual test head data","type":"object","description":"This field determines the data each VTH at the designated ServiceTask will receive.\nSee the example for more information","example":{"ServiceTask_123":{"vthArg1":"An argument your VTH expects.","vthArg2":{}},"ServiceTask_456":{"vthArg1":"An argument your VTH expects."}}},"async":{"type":"boolean"},"asyncTopic":{"type":"string"},"asyncMode":{"type":"string"}},"description":"The model for a test instance creation request."}}}}
\ No newline at end of file
diff --git a/otf-service-api/swagger.yml b/otf-service-api/swagger.yml
new file mode 100644 (file)
index 0000000..7bae19f
--- /dev/null
@@ -0,0 +1,714 @@
+openapi: 3.0.1\r
+info:\r
+  title: Open Test Framework API\r
+  description: A RESTful API used to communicate with the OTF test control unit.\r
+  contact:\r
+    name: OTF\r
+    url: https://localhost:32524\r
+  version: "1.0"\r
+tags:\r
+- name: Health Service\r
+  description: Query the availability of the API\r
+- name: Test Execution Service\r
+  description: Query the status and history of your test executions\r
+- name: Test Instance Service\r
+  description: Create, execute,and query test instances\r
+- name: Test Strategy Service\r
+  description: Deploy and delete test strategies to and from the test control unit.\r
+    (This documentation will only be available to the development team)\r
+paths:\r
+  /otf/api/health/v1:\r
+    get:\r
+      tags:\r
+      - Health Service\r
+      summary: Checks if the test control unit is available\r
+      operationId: getHealth_1\r
+      responses:\r
+        200:\r
+          description: The test control unit is available\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/OtfApiResponse'\r
+  /otf/api/testExecution/v1/executionId/{executionId}:\r
+    get:\r
+      tags:\r
+      - Test Execution Service\r
+      operationId: getExecutionStatus_1\r
+      parameters:\r
+      - name: executionId\r
+        in: path\r
+        required: true\r
+        schema:\r
+          type: string\r
+      - name: Authorization\r
+        in: header\r
+        schema:\r
+          type: string\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+  /otf/api/testInstance/execute/v1/id/{testInstanceId}:\r
+    post:\r
+      tags:\r
+      - Test Instance Service\r
+      summary: Executes a test instance by it's unique identifier\r
+      operationId: execute_1\r
+      parameters:\r
+      - name: testInstanceId\r
+        in: path\r
+        description: A string representation of a BSON ObjectId\r
+        required: true\r
+        schema:\r
+          type: string\r
+          description: The UUID of the test instance\r
+          format: uuid\r
+        example: 12345678912345678912345f\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      requestBody:\r
+        content:\r
+          application/json:\r
+            schema:\r
+              $ref: '#/components/schemas/ExecuteTestInstanceRequest'\r
+      responses:\r
+        200:\r
+          description: A successful synchronously executed test returns a test execution\r
+            object\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestExecutionResult'\r
+        201:\r
+          description: |-\r
+            A successful asynchronously executed test with asyncMode set to 'poll' returns an execution identifier\r
+            The identifier can be used as a parameter to the Test Execution Service to check the status of the executed test\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestExecutionResult'\r
+        401:\r
+          description: The mechanized identifier used with the request is prohibited\r
+            from accessing the resource.\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/OtfApiResponse'\r
+  /otf/api/testInstance/v1/id/{id}:\r
+    get:\r
+      tags:\r
+      - Test Instance Service\r
+      operationId: findById_1\r
+      parameters:\r
+      - name: id\r
+        in: path\r
+        description: A string representation of a BSON ObjectId\r
+        required: true\r
+        schema:\r
+          type: string\r
+          description: The UUID of the test instance\r
+          format: uuid\r
+        example: 12345678912345678912345f\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+  /otf/api/testInstance/create/v1/testDefinitionId/{testDefinitionId}/version/{version}:\r
+    post:\r
+      tags:\r
+      - Test Instance Service\r
+      summary: Create a test instance using the specified version of the test definition\r
+      operationId: createByTestDefinitionIdAndVersion_1\r
+      parameters:\r
+      - name: testDefinitionId\r
+        in: path\r
+        description: A string representation of a BSON ObjectId\r
+        required: true\r
+        schema:\r
+          type: string\r
+          description: The UUID of the test definition.\r
+          format: uuid\r
+        example: 12345678912345678912345f\r
+      - name: version\r
+        in: path\r
+        description: The version of the test definition used to create the instance\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: 2\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      - name: execute\r
+        in: query\r
+        description: Execute the test instance after it is created\r
+        allowEmptyValue: true\r
+        schema:\r
+          type: boolean\r
+        example: true\r
+      requestBody:\r
+        content:\r
+          application/json:\r
+            schema:\r
+              $ref: '#/components/schemas/CreateTestInstanceRequest'\r
+      responses:\r
+        201:\r
+          description: The created Test Instance object is returned when it is created\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestInstance'\r
+  /otf/api/testInstance/v1/testInstanceName/{testInstanceName}:\r
+    get:\r
+      tags:\r
+      - Test Instance Service\r
+      summary: Finds a test instance by it's name\r
+      operationId: findByTestInstanceName_1\r
+      parameters:\r
+      - name: testInstanceName\r
+        in: path\r
+        description: The name of the test instance to retrieve\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: myTestInstance\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      responses:\r
+        200:\r
+          description: A test instance object is returned when if it is found\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestInstance'\r
+  /otf/api/testInstance/v1/processDefinitionKey/{processDefinitionKey}:\r
+    get:\r
+      tags:\r
+      - Test Instance Service\r
+      operationId: findByProcessDefKey_1\r
+      parameters:\r
+      - name: processDefinitionKey\r
+        in: path\r
+        description: The process definition key associated with the test definition\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: someUniqueProcessDefinitionKey\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+  /otf/api/testInstance/create/v1/testDefinitionId/{testDefinitionId}:\r
+    post:\r
+      tags:\r
+      - Test Instance Service\r
+      summary: Create a test instance using the latest version of the test definition\r
+      operationId: createByTestDefinitionId_1\r
+      parameters:\r
+      - name: testDefinitionId\r
+        in: path\r
+        description: A string representation of a BSON ObjectId\r
+        required: true\r
+        schema:\r
+          type: string\r
+          description: The UUID of the test definition\r
+          format: uuid\r
+        example: 12345678912345678912345f\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      - name: execute\r
+        in: query\r
+        description: Execute the test instance after it is created\r
+        allowEmptyValue: true\r
+        schema:\r
+          type: boolean\r
+        example: true\r
+      requestBody:\r
+        content:\r
+          application/json:\r
+            schema:\r
+              $ref: '#/components/schemas/CreateTestInstanceRequest'\r
+      responses:\r
+        201:\r
+          description: The created Test Instance object is returned when it is created\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestInstance'\r
+  /otf/api/testInstance/v1/processDefinitionKey/{processDefinitionKey}/version/{version}:\r
+    get:\r
+      tags:\r
+      - Test Instance Service\r
+      operationId: findByProcessDefKeyAndVersion_1\r
+      parameters:\r
+      - name: processDefinitionKey\r
+        in: path\r
+        description: The process definition key associated with the test definition\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: someUniqueProcessDefinitionKey\r
+      - name: version\r
+        in: path\r
+        description: The version of the test definition used to create the instance\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: 2\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+  /otf/api/testInstance/create/v1/processDefinitionKey/{processDefinitionKey}/version/{version}:\r
+    post:\r
+      tags:\r
+      - Test Instance Service\r
+      summary: Create a test instance using the specified version of the test definition\r
+      operationId: createByProcessDefKeyAndVersion_1\r
+      parameters:\r
+      - name: processDefinitionKey\r
+        in: path\r
+        description: The process definition key associated with the test definition\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: someUniqueProcessDefinitionKey\r
+      - name: version\r
+        in: path\r
+        description: The version of the test definition used to create the instance\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: 2\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      - name: execute\r
+        in: query\r
+        description: Execute the test instance after it is created\r
+        allowEmptyValue: true\r
+        schema:\r
+          type: boolean\r
+        example: true\r
+      requestBody:\r
+        content:\r
+          application/json:\r
+            schema:\r
+              $ref: '#/components/schemas/CreateTestInstanceRequest'\r
+      responses:\r
+        201:\r
+          description: The created Test Instance object is returned when it is created\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestInstance'\r
+  /otf/api/testInstance/create/v1/processDefinitionKey/{processDefinitionKey}:\r
+    post:\r
+      tags:\r
+      - Test Instance Service\r
+      summary: Create a test instance using the latest version of the test definition\r
+      operationId: createByProcessDefKey_1\r
+      parameters:\r
+      - name: processDefinitionKey\r
+        in: path\r
+        description: The process definition key associated with the test definition\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: someUniqueProcessDefinitionKey\r
+      - name: Authorization\r
+        in: header\r
+        description: Base64 encoded Application Authorization Framework credentials\r
+        required: true\r
+        schema:\r
+          type: string\r
+        example: Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=\r
+      - name: execute\r
+        in: query\r
+        description: Execute the test instance after it is created\r
+        allowEmptyValue: true\r
+        schema:\r
+          type: boolean\r
+        example: true\r
+      requestBody:\r
+        content:\r
+          application/json:\r
+            schema:\r
+              $ref: '#/components/schemas/CreateTestInstanceRequest'\r
+      responses:\r
+        201:\r
+          description: The created Test Instance object is returned when it is created\r
+          content:\r
+            application/json:\r
+              schema:\r
+                $ref: '#/components/schemas/TestInstance'\r
+  /otf/api/testStrategy/delete/v1/deploymentId/{deploymentId}:\r
+    delete:\r
+      tags:\r
+      - Test Strategy Service\r
+      operationId: deleteByDeploymentId_1\r
+      parameters:\r
+      - name: deploymentId\r
+        in: path\r
+        required: true\r
+        schema:\r
+          type: string\r
+      - name: authorization\r
+        in: header\r
+        schema:\r
+          type: string\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+  /otf/api/testStrategy/delete/v1/testDefinitionId/{testDefinitionId}:\r
+    delete:\r
+      tags:\r
+      - Test Strategy Service\r
+      operationId: deleteByTestDefinitionId_1\r
+      parameters:\r
+      - name: testDefinitionId\r
+        in: path\r
+        required: true\r
+        schema:\r
+          type: string\r
+      - name: authorization\r
+        in: header\r
+        schema:\r
+          type: string\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+  /otf/api/testStrategy/deploy/v1:\r
+    post:\r
+      tags:\r
+      - Test Strategy Service\r
+      operationId: deployTestStrategy_1\r
+      parameters:\r
+      - name: Authorization\r
+        in: header\r
+        schema:\r
+          type: string\r
+      requestBody:\r
+        content:\r
+          multipart/form-data:\r
+            schema:\r
+              type: object\r
+              properties:\r
+                bpmn:\r
+                  type: object\r
+                resources:\r
+                  type: object\r
+                testDefinitionId:\r
+                  type: string\r
+                testDefinitionDeployerId:\r
+                  type: string\r
+                definitionId:\r
+                  type: string\r
+      responses:\r
+        default:\r
+          description: default response\r
+          content:\r
+            application/json: {}\r
+components:\r
+  schemas:\r
+    ApiResponse:\r
+      type: object\r
+      properties:\r
+        code:\r
+          type: integer\r
+          format: int32\r
+        date:\r
+          type: string\r
+          format: date-time\r
+        message:\r
+          type: string\r
+    JSONObject:\r
+      type: object\r
+    ObjectId:\r
+      type: object\r
+      properties:\r
+        timestamp:\r
+          type: integer\r
+          format: int32\r
+        machineIdentifier:\r
+          type: integer\r
+          format: int32\r
+        processIdentifier:\r
+          type: integer\r
+          format: int32\r
+        counter:\r
+          type: integer\r
+          format: int32\r
+        time:\r
+          type: integer\r
+          format: int64\r
+        date:\r
+          type: string\r
+          format: date-time\r
+        timeSecond:\r
+          type: integer\r
+          format: int32\r
+    TestExecution:\r
+      type: object\r
+      properties:\r
+        get_id:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        executionId:\r
+          type: string\r
+        testResult:\r
+          type: string\r
+        testDetails:\r
+          type: object\r
+          additionalProperties:\r
+            type: object\r
+        startTime:\r
+          type: string\r
+          format: date-time\r
+        endTime:\r
+          type: string\r
+          format: date-time\r
+        async:\r
+          type: boolean\r
+        asyncTopic:\r
+          type: string\r
+        asyncMode:\r
+          type: string\r
+        executor:\r
+          type: string\r
+        groupId:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        testInstanceId:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        testInstance:\r
+          type: object\r
+          additionalProperties:\r
+            type: object\r
+        testHeadResults:\r
+          type: array\r
+          items:\r
+            $ref: '#/components/schemas/TestHeadResult'\r
+        testDetailsJSON:\r
+          type: string\r
+        testInstanceJSON:\r
+          type: string\r
+    TestExecutionResult:\r
+      type: object\r
+      properties:\r
+        testExecution:\r
+          $ref: '#/components/schemas/TestExecution'\r
+        executionId:\r
+          type: string\r
+        testCompleted:\r
+          type: boolean\r
+        testExists:\r
+          type: boolean\r
+    TestHeadResult:\r
+      type: object\r
+      properties:\r
+        testHeadId:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        testHeadName:\r
+          type: string\r
+        bpmnVthTaskId:\r
+          type: string\r
+        testHeadResponse:\r
+          type: object\r
+          additionalProperties:\r
+            type: object\r
+        startTime:\r
+          type: string\r
+          format: date-time\r
+        endTime:\r
+          type: string\r
+          format: date-time\r
+        testHeadResponseJSON:\r
+          $ref: '#/components/schemas/JSONObject'\r
+    ExecuteTestInstanceRequest:\r
+      type: object\r
+      properties:\r
+        async:\r
+          type: boolean\r
+          writeOnly: true\r
+        asyncTopic:\r
+          title: Execute the test synchronously or asynchronously..\r
+          type: string\r
+          description: Ignored unless async is true, and asyncMode is DMaaP.\r
+          example: MyDMaaPTopic.\r
+        asyncMode:\r
+          title: Set the asynchronous execution mode.\r
+          type: string\r
+          description: Ignored unless async is true. The poll mode will return an\r
+            executionId that can be used to query the result of the executed test.\r
+            DMaaP is currently unsupported.\r
+          example: POLL\r
+          enum:\r
+          - POLL\r
+          - DMAAP\r
+        testData:\r
+          title: Use an existing test instance with different global test data.\r
+          type: object\r
+          description: Overrides (not overwrites) the testData field for the requested\r
+            execution. The overridden data will be preserved in the test execution\r
+            result.\r
+          example:\r
+            globalVar1: I'm available to your workflow!\r
+            globalVar2:\r
+              me: too\r
+        vthInput:\r
+          title: Use an existing test instance with different inputs to your VTHs.\r
+          type: object\r
+          description: Overrides (not overwrites) the vthInput field for the requested\r
+            execution. The overridden data will be preserved in the test execution\r
+            result.\r
+          example:\r
+            ServiceTask_123:\r
+              vthArg1: An argument your VTH expects.\r
+              vthArg2: {}\r
+            ServiceTask_456:\r
+              vthArg1: An argument your VTH expects.\r
+      description: The model2 for a test instance execution request.\r
+    TestInstance:\r
+      type: object\r
+      properties:\r
+        get_id:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        testInstanceName:\r
+          type: string\r
+        testInstanceDescription:\r
+          type: string\r
+        groupId:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        testDefinitionId:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        processDefinitionId:\r
+          type: string\r
+        useLatestTestDefinition:\r
+          type: boolean\r
+        testData:\r
+          type: object\r
+          additionalProperties:\r
+            type: object\r
+        vthInput:\r
+          type: object\r
+          additionalProperties:\r
+            type: object\r
+        internalTestData:\r
+          type: object\r
+          additionalProperties:\r
+            type: object\r
+        createdAt:\r
+          type: string\r
+          format: date-time\r
+        updatedAt:\r
+          type: string\r
+          format: date-time\r
+        createdBy:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        updatedBy:\r
+          $ref: '#/components/schemas/ObjectId'\r
+        vthInputJSON:\r
+          $ref: '#/components/schemas/JSONObject'\r
+        testDataJSON:\r
+          $ref: '#/components/schemas/JSONObject'\r
+        internalTestDataJSON:\r
+          $ref: '#/components/schemas/JSONObject'\r
+    CreateTestInstanceRequest:\r
+      required:\r
+      - testData\r
+      - testInstanceDescription\r
+      - testInstanceName\r
+      type: object\r
+      properties:\r
+        testInstanceName:\r
+          title: Name the test instance\r
+          type: string\r
+          description: The name must be unique among all test instances belonging\r
+            to the same test definition.\r
+          example: MyTestInstance\r
+        testInstanceDescription:\r
+          title: Describe the test instance being created\r
+          type: string\r
+          description: Use this field to describe the functionality of the test instance\r
+          example: This test instance does absolutely nothing!\r
+        testData:\r
+          title: Set global variables\r
+          type: object\r
+          description: |-\r
+            This field has read and write access by any task within the workflow.\r
+            See the example for more information\r
+          example:\r
+            globalVar1: I'm available to your workflow!\r
+            globalVar2:\r
+              me: too\r
+        vthInput:\r
+          title: Set virtual test head data\r
+          type: object\r
+          description: |-\r
+            This field determines the data each VTH at the designated ServiceTask will receive.\r
+            See the example for more information\r
+          example:\r
+            ServiceTask_123:\r
+              vthArg1: An argument your VTH expects.\r
+              vthArg2: {}\r
+            ServiceTask_456:\r
+              vthArg1: An argument your VTH expects.\r
+        async:\r
+          type: boolean\r
+        asyncTopic:\r
+          type: string\r
+        asyncMode:\r
+          type: string\r
+      description: The model2 for a test instance creation request.\r
index 84e31c1..6737bd3 100644 (file)
@@ -1,9 +1,13 @@
 FROM python:2.7\r
 \r
-ARG HTTP_PROXY="localhost:8080"\r
-ARG HTTPS_PROXY="localhost:8080"\r
-ARG http_proxy="localhost:8080"\r
-ARG https_proxy="localhost:8080"\r
+# ARG HTTP_PROXY="localhost:8080"\r
+# ARG HTTPS_PROXY="localhost:8080"\r
+# ARG http_proxy="localhost:8080"\r
+# ARG https_proxy="localhost:8080"\r
+\r
+ENV NAMESPACE=namespace\r
+ENV APP_NAME=otf-ping-test-head\r
+ENV APP_VERSION=1.0\r
 \r
 RUN python --version\r
 \r